The first alpha for Python 3.4 was released yesterday [link]. Two of the major changes to 3.4 were new additions to the standard library: functools.singledispatch and enum.Enum.
Enumerations (Enums) are a way to define a finite set of cases or states with no programmatic ordering. For example, we could have an Enum class with members ["apple", "banana", and "orange"], but it wouldn’t make sense to have an Enum class with members [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] because these have a programatic ordering and are better expressed as integers. Enums have been around in other languages (like C#) for years. There are many times when it’s useful to have a variable representing a particular state, and Python developers have had to find ways around it. Tkinter, for example, has N, S, W, E as cardinal directions used for anchoring widgets. These are stored as integers.
Up until Python 3.4, there were two prominent ways of implementing Enums: global constants, and class variables. Module-level enumeration states are almost always stored as globals. For example:
>>> RED, GREEN, BLUE = range(3) >>> ORANGE, BANANA, APPLE = range(3)
This works well enough, although I’ve intentionally provided an example where it causes problems. Is ORANGE a color or a fruit?
>>> color = RED >>> color == RED True >>> color == ORANGE True >>> color + GREEN == BANANA True >>> # Completely nonsensical
Another option is to use classes to get around this:
>>> class Color(object): red = 1 green = 2 blue = 3 >>> class Fruit(object): orange = 1 banana = 2 apple = 3
This sort of deal with the ambiguity of the word orange:
>>> color = Color.red >>> color == Fruit.orange # no one would make this comparison True
So at least now the members are assigned to a class rather than just global integers. But comparisons between makeshift enum classes still give undesirable results, and we can still perform integer operations on them.
Enter the enum module.
>>> # The new way of doing things: >>> from enum import Enum >>> Color = Enum("Color", "red green blue orange black") >>> Color.red >>> Color.red + Color.green Traceback (most recent call last): File "<pyshell#16>", line 1, in Color.red + Color.green TypeError: unsupported operand type(s) for +: 'Color' and 'Color' >>> # That makes a lot more sense!
This is a huge improvement over the old way of implementing Enums. We can’t add enum members – not without defining a special __add__ method for our class, anyway! However, this is certainly possible, if you want to define a custom __add__ so that Color.red plus Color.blue is Color.purple. Also, Color.red won’t evaluate as equal to anything but itself, regardless of what its internal integer value is.
Wait, its what? Now that we have a dedicated Enum class, why do we need integer values associated with enumeration members? The main reason is for backwards compatibility. Some code relies on modules’ integer values their enum members. In fact, the enum module also provides an IntEnum class, where members can be treated as ints, specifically to support backwards compatibility. Another benefit of having integer values associated with enum members is the ability to include aliases, such as aubergine and eggplant in this example:
>>> class Food(Enum): apple = 1 banana = 2 orange = 3 aubergine = 4 eggplant = 4 # Synonyms can be aliases def eat(self): print "nom!" >>> Color.orange == Food.orange False >>> Color.orange.name == Food.orange.name True >>> Color.red == Food.apple False >>> Color.red.value == Food.apple.value True >>> for food in Food: print(food) Food.apple Food.banana Food.orange Food.aubergine Food.eggplant >>> type(Food.eggplant) <enum 'Food'> >>> isinstance(Food.eggplant, int) False >>> Food.banana.eat() nom!
This offers a much more intuitive way to deal with Enums than we have in python 2.7.
Python 3.4′s Enum implementation is definitely better than what we have been using. However, it still relies on externally associating enumeration members with integers. This is mainly because the Enum class is long overdue, and there have been so many workarounds implemented. Also, there’s no reason the enum module can’t be ported to Python 2.7, other than that the core developers really want us to migrate to 3K. I don’t blame them – it has a lot to offer. But there just isn’t enough traction yet, and it may be worth it to include Enum in the Python 2.7 standard library. Or perhaps as a 3rd party module… Thoughts?