Enumerations in python

51,730

Solution 1

class Directions:
    up = 0
    down = 1
    left = 2
    right =3

Solution 2

UPDATE 1: Python 3.4 will have a built-in well designed enum library. The values always know their name and type; there is an integer-compatible mode but the recommended default for new uses are singletons, unequal to any other object.

UPDATE 2: Since writing this I realized the critical test for enums is serialization. Other aspects can be refactored later, but if your enum goes into files / onto the wire, ask yourself up front what should happen if it's deserialized by an older/newer version (that might support a different set of values)...


If you are sure that you need an enum, others have answered how to do it. But let's see why you want them? Understanding the motivation will help with choosing the solution.

  • Atomic values - in C, small numbers are easy to pass around, strings aren't. In Python, strings like "up" are perfectly good for many uses. Moreover, any solution that ends up with just a number is worse for debugging!

  • Meaningful values - in C, you frequently have to deal with existing magic numbers, and just want some syntax sugar for that. That's not the case here. However, there is other meaningful information you might want to associate with directions, e.g. the (dx,dy) vector - more on that below.

  • Type checking - in C, enums help catching invalid values at compile time. But Python generally prefers sacrificing compiler checking for less typing.

  • Introspection (doesn't exist in C enums) - you want to know all the valid values.

  • Completion - the editor can show you the possible values and help you type them.

Strings Redeemed (aka Symbols)

So, on the light side of Pythonic solutions, just use strings, and maybe have a list/set of all valid values:

DIRECTIONS = set(['up', 'down', 'left', 'right'])

def move(self, direction):
    # only if you feel like checking
    assert direction in DIRECTIONS
    # you can still just use the strings!
    if direction == 'up':
        # Do something

Note that the debugger would tell you that the function was called with 'up' as its argument. Any solution where direction is actually 0 is much worse than this!

In the LISP family of languages, this usage is dubbed symbols - atomic objects usable as easily as numbers would be, but carrying a textual value. (To be precise, symbols are string-like but a separate type. However, Python routinely uses regular strings where LISP would use symbols.)

Namespaced Strings

You can combine the idea that 'up' is better than 0 with the other solutions.

If you want to catch mispellings (at run time):

UP = 'up'
...
RIGHT = 'right'

And if you want to insist on typing a prefix to get completion, put the above in a class:

class Directions:
    UP = "up"
    ...
    RIGHT = "right"

or just in a separate file, making it a module.

A module allows lazy users to do from directions import * to skip the prefix - up to you whether you consider this a plus or minus... (I personally would hate to be forced to type Directions.UP if I'm using it frequently).

Objects with functionality

What if there is useful information/functionality associated with each value? "right" is not just one of 4 arbitrary values, it's the positive direction on the X axis!

If what you are doing in that if is something like:

def move(self, direction):
    if direction == 'up':
        self.y += STEP
    elif direction == 'down':
        self.y -= STEP
    elif direction == 'left':
        self.x -= STEP
    elif direction == 'right':
        self.x += STEP

than what you'd really like to write is:

def move(self, direction):
    self.x += direction.dx * STEP
    self.y += direction.dy * STEP

and that's it!

So you want to stuff this into either instances:

# Written in full to give the idea.
# Consider using collections.namedtuple
class Direction(object):
    def __init__(self, dx, dy, name):
        self.dx = dx
        self.dy = dy
        self.name = name
    def __str__(self):
        return self.name

UP = Direction(0, 1, "up")
DOWN = Direction(0, -1, "down")
LEFT = Direction(-1, 0, "left")
RIGHT = Direction(1, 0, "right")

or just classes:

class Direction(object):
    pass

class Up(Direction):
    dx = 0
    dy = 1

...

class Right(Direction):
    dx = 1
    dy = 0

Remember that in Python, classes are also objects (distinct from any other object), and you can compare them: direction == Up etc.

Generally, instances are probably cleaner, but if your enumerated concepts have some hierarchical relationship, sometimes modeling them directly with classes is very nice.

Solution 3

I gave a +1 to Kugel, but another leaner option is

dirUp, dirDown, dirLeft, dirRight = range(4)
      • (Some time passes)

So I was thinking...we have an obvious DRY violation here in that we already specify four items on the LHS, and then again specify four on the RHS. What happens if we add items in the future? What happens when someone else adds them, and maybe they are more sloppy than ourselves? One obvious way to remove the DRY violation is to use the list of enums themselves to assign their values:

>>> enums = ['dirUp', 'dirDown']
>>> for v, k in enumerate(enums):
...     exec(k + '=' + str(v))
...     
>>> print dirDown
1
>>> print dirUp
0

If you can stomach using exec() for this, then fine. If not, then use the other approach. This current discussion is all academic anyway. However, there is still a problem here. What if the enums are used throughout a great body of source code, and some other programmer comes along and inserts a new value between dirUp and dirDown? This will cause misery because the mapping between the names of the enums and the enums themselves will be wrong. Bear in mind that that remains a problem even in the original simple solution.

Here, we have the novel idea of using the builtin hash() function to determine our enum value as an int, and we use the text name of the enum itself to determine the hash:

>>> for k in enums:
...     exec(k + '=' + str(hash(k)))
... 
>>> dirUp
-1147857581
>>> dirDown
453592598
>>> enums = ['dirUp', 'dirLeft', 'dirDown']
>>> for k in enums:
...     exec(k + '=' + str(hash(k)))
... 
>>> dirUp
-1147857581
>>> dirDown
453592598
>>> dirLeft
-300839747
>>> 

Notice that we inserted a new value between dirUp and dirDown, i.e. dirLeft, and our original mapping values for the first two did not change.

I may actually use this in my own code. Thanks to the OP for posting the question.

      • (Some more time passes)

Beni Cherniavsky-Paskin made some very good comments:

  • Python's default hash() is not stable across platforms (dangerous for persistence applications)
  • The possibility of collisions is always present.

I tend to agree with both observations. His suggestion is to use the strings themselves (I really like the self-documenting behaviour of using the values) as the hash, and so the code becomes the following (note we use a set instead of a list, to enforce uniqueness):

>>> items=('dirUp','dirDown','dirLeft','dirRight')
>>> for i in items:
        exec('{}="{}"'.format(i,i))
>>> dirDown
'dirDown'

It is also trivial to put these in a namespace, so as to avoid collisions with other code:

>>> class Direction():
        for i in ('dirUp','dirDown','dirLeft','dirRight'):
            exec('{}="{}"'.format(i,i))

>>> Direction.dirUp
'dirUp'

The length of the cryptographic hash he mentions can be seen here:

>>> from hashlib import md5
>>> crypthash = md5('dirDown'.encode('utf8'))
>>> crypthash.hexdigest()
'6a65fd3cd318166a1cc30b3e5e666d8f'

Solution 4

The collections.namedtuple object can provide such a namespace:

>>> import collections
>>> dircoll=collections.namedtuple('directions', ('UP', 'DOWN', 'LEFT', 'RIGHT'))
>>> directions=dircoll(0,1,2,3)
>>> directions
directions(UP=0, DOWN=1, LEFT=2, RIGHT=3)
>>> directions.DOWN
1
>>> 

Solution 5

This is simple and effective:

class Enum(object):
  def __init__(self, *keys):
    self.__dict__.update(zip(keys, range(len(keys))))

Usage:

>>> x = Enum('foo', 'bar', 'baz', 'bat')
>>> x.baz
2
>>> x.bat
3
Share:
51,730
Justin
Author by

Justin

html-query-plan - Display SQL Server Execution Plans in HTML

Updated on September 24, 2021

Comments

  • Justin
    Justin over 2 years

    Duplicate:
    What’s the best way to implement an ‘enum’ in Python?

    Whats the recognised way of doing enumerations in python?

    For example, at the moment I'm writing a game and want to be able to move "up", "down", "left" and "right". I'm using strings because I haven't yet figured out how enumerations work in python, and so my logic is littered with things like this:

    def move(self, direction):
        if direction == "up":
            # Do something
    

    I want to replace "up" with something like Directions.up