Python Tips and Gotchas #1 - Replace literals with constants in if-expressions

Whenever you need to check the value of a basic data type, the most straight-forward solution is to use literals:

1
2
3
4
if direction == 'up':
  # ...
elif direction == 'down':
  # ...

This might seem like pythonic code, but its simplicity could bite you. If you have a lot of conditions and perhaps in multiple locations in your code, what happens if you mistype any of those string literals? Python will not complain, and there will not be any runtime errors. But you will end up with logical errors that could be insanely hard to track down.

Numeric literals are double trouble as the also comes with the issue of magic numbers. In the following example we not only have to guess what the number 545 really means, but it is almost easier to mistype than a string since we can not use common sense to detect a spelling error:

1
2
if error_code == 545:
  # ...

In order to make your code more readable and to make it crash on any typos, using constants is often recommended. Simply extract the raw values into appropriately located constants (in Python just uppercased variables) and use the constants in the all if-expressions:

1
2
3
4
5
UP = 'up'
DOWN = 'down'

if direction == UP:
  # ...

And with numeric literals:

1
2
3
4
5
SERVER_DOWN = 100
CONNECTION_ERROR = 200

if error_code == SERVER_DOWN:
  # ...

In the first example Python will throw a NameError if you by mistake type “UPP”, “uP” or “OP”. Had we used string literals, Python would have had no way to recognize your typing error.

In larger projects you could group all constants in one or more modules. Since Python 3.4 there is also a new tool to help organize this: enums.

Enums are declared as classes but inherit from enum.Enum. This allows us to group related constants under a canonical name:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import enum

class ErrorCode(enum.Enum):
  SERVER_DOWN = 100
  CONNECTION_ERROR = 200

error = 100

if error == ErrorCode.SERVER_DOWN.value:
    print('Server is down')

Enums can be compared with the is keyword. If you are in control of how each operand in a comparison is created, you could therefor use enums throughout your code to increase the readability even futher:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import enum

class ErrorCode(enum.Enum):
  SERVER_DOWN = 100
  CONNECTION_ERROR = 200

error = ErrorCode.CONNECTION_ERROR

if error is ErrorCode.CONNECTION_ERROR:
    print('There is a connetion error')

Do however keep in mind that enum attribute lookups are slower than regular attributes lookups.