Loading

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.

Copyright © 2017 Roadside Software & Adventures / All rights reserved.