Pages

Sep 25, 2013

First look at Python enums (part 1)

The major novelty in the upcoming Python 3.4 is the addition of an Enum type. If you've ever written code in C, C#, C++ or Java, you've already encountered enumerations and know what they are. If not, here's how Wikipedia defines them:

[A]n enumerated type (also called enumeration or enum […]) is a data type consisting of a set of named values called elements, members or enumerators of the type. The enumerator names are usually identifiers that behave as constants in the language. A variable that has been declared as having an enumerated type can be assigned any of the enumerators as a value.


It'll all make more sense once we start writing some code, but first there's some preliminary work to do.


Installing Enums

As mentioned Enums are available starting Python 3.4. At the time of this writing, the alpha2 has been published so you can go grab it (and start bug hunting). Enums have also been backported to every version after 2.4 in a separate package called enum34 available on PyPI.

Here's how to install it with pip and virtualenv:

$ virtualenv python_enums
...
$ source python_enums/bin/activate
(python_enums)$ pip install enum34
...

You can validate that the module has been installed correctly by trying to import it. It should not give an error:

(python_enums)$ python
>>> import enum
>>>

Creating our first Enum

As we've seen in the Wikipedia definition, defining an Enum consists of defining a type that can only take a finite set of values. In Python this is done by subclassing the Enum class. For instance:

from enum import Enum

class WeekDays(Enum):
    Monday = 1
    Tuesday = 2
    Wednesday = 3
    Thursday = 4
    Friday = 5
    Saturday = 6
    Sunday = 7

Make sure you get the terminology correctly: we are defining an enum(eration) called WeekDays that has 7 separate members.


An enum member has a type

The members of WeekDays are objects whose type is an Enum WeekDays. Or to define things in Python terms, assume these assertions are true:

assert(type(WeekDays.Monday) == "<Enum 'WeekDays'>")
assert(isinstance(WeekDays.Monday, WeekDays))

An enum member has a name

Any member of an enumeration can return its name as a string by getting the `name` attribute:

assert(WeekDays.Monday.name == 'Monday')
assert(isinstance(WeekDays.Monday.name, str))

The name can be used to retrieve a member by item access:

assert(WeekDays["Monday"] is WeekDays.Monday)

Note that we are checking for similarity by comparing identities and not values. We'll cover this some more in later paragraphs.


An enum member has a value

You might have noticed when defining or WeekDays Enum that we're assigning a value for each member of the enumeration. These values aren't really meant to mean or represent anything, especially since enumeration members don't support comparison. We'll talk in length about comparison, values and identity in the second part of this article. For now, just remember that you need to give a distinct value for each member. In most cases, numerical values in increasing orders would be enough.


If the value is meaningless, why do we explicit it?

It has been discussed that it would make it easier to have the enum assign a random value to each member implicitly.

Setting aside the challenges that might occur from ensuring that each of these value is unique, the Python dev have opted out, since it would take too much "magic" to make it happen, and would hide too much the details of implementation.

After all, the Zen of Python tells us that "explicit is better than implicit", right?


Access

You can access the value of a enum member by examining the value attribute:

assert(WeekDays.Monday.value == 1)

The value can be used to retrieve a member by calling the class:

assert(WeekDays(1) is WeekDays.Monday)

Functional API

It is possible to assign numerical values of increasing orders automatically, using what has been called the "Functional API".
To understand the syntax, here's the exact same definition of the WeekDays enum, using the functional API:

Enum("WeekDays", "Monday Tuesday Wednesday Thursday Friday Saturday Sunday")

Enum supports iteration

Iterating over the members of an Enum is pretty straightforward:

>>>  for day in WeekDays:
...      print day, day.name, day.value
WeekDays.Monday Monday 1
WeekDays.Tuesday Tuesday 2
WeekDays.Wednesday Wednesday 3
WeekDays.Thursday Thursday 4
WeekDays.Friday Friday 5
WeekDays.Saturday Saturday 6
WeekDays.Sunday Sunday 7

Note that the order of iteration is the same as the order of definition of the elements, regardless of the values assigned.


Aliasing a member

A name is unique but a value is not. Different elements may have the same value as part of an alias mechanism.

If A is defined before B, B is an alias for A.
Lookup by value returns A.
Lookup by name for B returns A.

B doesn't appear when iterating over the Enum.