Display names for Python's new Enum class
A few weeks ago, Python 3.4 has been released and one of the added features is
a new module called enum
that provides several classes that can be used to
define enumerated values. An example for an enum could be:
from enum import Enum
class Country(Enum):
US = 'US'
AU = 'AU'
CA = 'CA'
That's not much of an improvement over just an ordinary class that with
object
as the base clase instead of Enum
. The advantages of the new
enum class is that it enforces some constaints on the class that prevent you
from using it as a regular class. For example, the enum class can only be
instantiated with a valid value:
>>> Country('US')
<Country.US: 'US'>
>>> Country('DE')
ValueError: DE is not a valid Country
The other notable advantage of using Enum
is comparing values. One example
would be a web from that passes a country code back when the form is POST'ed.
To compare the value, with our enum, we can simply create an instance of the
enum and catch the ValueError
above. We can also check if the country code
passed back to us is Australia or not and act accordingly:
>>> Country.AU == Country('AU')
True
It also prevents us from comparing apples and oranges:
class Apple(Enum):
GRANNY_SMITH = 1
GALA = 2
class Orange(Enum):
NAVEL = 1
VALENCIA = 2
>>> Apple.GRANNY_SMITH == 1
False
>>> Apple.GRANNY_SMITH == Orange(1)
False
>>> Apple.GRANNY_SMITH == Apple(1)
True
More details about the new enum
module can be found in the
Python 3.4 docs.
There's also a backport available on PyPI named enum34
which can be installed using pip install enum34
. Warning: There is also
a package enum
available that behaves differently from the backported
version so make sure you install the right one.
Giving Them Names
I spend most of my day working with Django and part of that involves defining "choices" for model fields that limits its values to a pre-defined set. Usually, that includes defining a class attribute for each value that acts as a constant and then define the corresponding display value:
class Address(models.Model):
CA = 'ca'
AU = 'au'
COUNTRIES = (
(CA, 'Canada'),
(AU, 'Australia'),
)
The display values are then used throughout the site whenever the set value
on a concrete Address
model.
So we had a bit of a discussion about this at work, wondering if this would be
something that Enum
could be used for or not. More precisely, if we could
use an enum to define the values and the printable representation without
having to define multiple classes. This made me curious. I opened up my beloved
tool ipython
and started playing around. After various unexpected
exceptions and some weird behaviours of my Country
class, I found a way to
make it work (also available as Gist):
class PrintableValue(object):
def __init__(self, value, printable_name):
self.value = value
self.printable_name = printable_name
class EnumWithName(Enum):
def __new__(cls, value):
obj = object.__new__(cls)
obj._value_ = value.value
obj.printable_name = value.printable_name
return obj
class Country(EnumWithName):
AU = PrintableValue('AU', 'Australia')
US = PrintableValue('US', 'United States of America')
CA = PrintableValue('CA', 'Canada')
I think it is quite neat and makes Enum
a viable alternative
to similar use cases. Maybe not necessarily for Django models because there
you might want the country codes to be accessible as Address.US
instead
of using another class to access it: Country.US
. Apart from that, I'm
pretty sure that there will soon be some of my code using Enum
and
benefitting from the additional constraints. And of course, the printable
names for each of these values.