Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enumerations in python [duplicate]

Tags:

python

enums

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

like image 730
Justin Avatar asked Dec 28 '09 11:12

Justin


2 Answers

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.

like image 172
Beni Cherniavsky-Paskin Avatar answered Oct 06 '22 15:10

Beni Cherniavsky-Paskin


class Directions:
    up = 0
    down = 1
    left = 2
    right =3
like image 48
Kugel Avatar answered Oct 06 '22 16:10

Kugel