enum in Python

Recently I was reading a post by Eli Bendersky (one of my favorite bloggers) and I ran across a sentence in which Eli says “It’s a shame Python still doesn’t have a functional enum type, isn’t it?”

The comment startled me because I had always thought that it was obvious how to do enums in Python, and that it was obvious that you don’t need any special language features to do it. Eli’s comment made me think that I might need to do a reality-check on my sense of what was and was not obvious about enums in Python.

So I googled around a bit and found that there are a lot of different ideas about how to do enums in Python. I found a very large set of suggestions on StackOverflow here and here and here. There is a short set of suggestion on Python Examples. The ActiveState Python Cookbook has a long recipe, and PEP-354 is a short proposal (that has been rejected). Surprisingly, I found only a couple of posts that suggested what had seemed to me to be THE obvious solution. The clearest was by snakile on StackOverflow.

Anyway, to end the suspense, the answer that seemed to me so obvious was this. An enum is an enumerated data type. An enumerated data type is a type, and a type is a class.

class           Color : pass
class Red      (Color): pass
class Yellow   (Color): pass
class Blue     (Color): pass

Which allows you to do things like this.

class Toy: pass

myToy = Toy()

myToy.color = "blue"  # note we assign a string, not an enum

if myToy.color is Color:
    pass
else:
    print("My toy has no color!!!")    # produces:  My toy has no color!!!

myToy.color = Blue   # note we use an enum

print("myToy.color is", myToy.color.__name__)  # produces: myToy.color is Blue
print("myToy.color is", myToy.color)           # produces: myToy.color is <class '__main__.Blue'>

if myToy.color is Blue:
    myToy.color = Red

if myToy.color is Red:
    print("my toy is red")   # produces: my toy is red
else:
    print("I don't know what color my toy is.")

So that’s what I came up with.

But with so many intelligent people all trying to answer the same question, and coming up with such a wide array of different answers, I had to fall back and ask myself a few questions.

  • Why am I seeing so many different answers to what seems like a simple question?
  • Is there one right answer? If so, what is it?
  • What is the way — the best, or most widely-used, or most pythonic — way to do enums in Python?
  • Is the question really as simple as it seems?

For me, the jury is still out on most of these questions, but until they return with a verdict I have come up with two thoughts on the subject.

First, I think that many programmers come to Python with backgrounds in other languages — C or C++, Java, etc. Their experiences with other languages shape their conceptions of what an enum — an enumerated data type — is. And when they ask “How can I do enums in Python?” they’re asking a question like the question that sparked the longest thread of answers on StackOverflow:

I’m mainly a C# developer, but I’m currently working on a project in Python. What’s the best way to implement the equivalent of an enum [i.e. a C# enum] in Python?

So naturally, the question “How can I implement in Python the equivalent of the kind of enums that I’m familiar with in language X?” has at least as many answers as there are values of X.

My second thought is somewhat related to the first.

Python developers believe in duck typing. So a Python developer’s first instinct is not to ask you:

What do you mean by “enum”?

A Python developer’s first instinct is to ask you:

What kinds of things do you think an “enum” should be able to do?
What kinds of things do you think you should be able to do with an “enum”?

And I think that different developers probably have very different ideas about what one should be able to do with an “enum”. Naturally, that leads them to propose different ways of implementing enums in Python.

As a simple example, consider the question — Should you be able to sort enums?

My personal inclination is to say that — in the most conceptually pure sense of “enum” — the concept of sorting enums makes no sense. And my suggestion for implementing enums in Python reflects this. Suppose you implement a “Color” enum using the technique that I’ve proposed, and then try to sort enums.

# how do enumerated values sort?
colors = [Red, Yellow, Blue]
colors.sort()
for color in colors:
    print(color.__name__)

What you get is this:

Traceback (most recent call last):
  File "C:/Users/ferg_s/pydev/enumerated_data_types/edt.py", line 32, in <module>
    colors.sort()
TypeError: unorderable types: type() < type()

So that suites me just fine.

But I can easily imagine someone (myself?) working with an enum for, say, Weekdays (Sunday, Monday, Tuesday… Saturday). And I think it might be reasonable in that situation to want to be able to sort Weekdays and to do greater than and less than comparisons on them.

So if we’re talking duck typing, I’m happy with enums/ducks that are motionless and silent. My only requirement is that they be different from everything else and different from each other. But I can easily imagine situations where one might reasonably need/want/prefer ducks that can form a conga line, dance, and sing a few bars. And for those situations, you obviously need more elaborate implementations of enums.

So, with these thoughts in mind, I’m inclined to think that there is no single, best way to implement an enum in Python. The concept of an enum is flexible enough to cover a variety of implementations offering a variety of features.

About these ads

8 thoughts on “enum in Python

  1. Your approach considers enums as singletons. Even then, surely the colours above should be instances of colour, not subclasses. issubclass(value, Color) is much stranger than isinstance(value, Color); and I don’t even know what “if myToy.color is Color” is meant to mean.

    Of course you can use inheritance for complicated enums if you need its properties, but otherwise the following seems much more sensible:

    class Color(object):
    __slots__ = (‘name’,)
    def __init__(self, name):
    self.name = name
    def __repr__(self):
    return ‘{0}({1!r})’.format(self.__class__.__name__, self.name)

    RED = Color(‘red’)

    etc.

    An even more useful definition might allow for:

    RED = Color(‘red’, 1.0, 0.0, 0.0)
    hue, sat, bri = RED.hsv

    Standard colours are a particularly nice case for these singleton-style enums.

  2. A very common requirement is the ability to have numbers/constants associated with each member, manually or automatically allocated, and the ability to translate between the numbers and members bidirectionally. This is the model in Java/C/C++/C# although you may have to build the mapping yourself. A second nice to have is that when handling the members there is automatic detection/warning when you forget to handle one of the members.

    Getting all that is nicest when it is builtin to the language/compiler/interpreter. When you have to construct things yourself in a language that doesn’t natively support them you’ll always end up with compromises and omissions. And that is why there are so many incompatible alternatives for python.

    • If you don’t need the numbers to be meaningful, composable, countable or communicable outside of the process, then using the singleton approach, where each instance has a unique Python ID is sufficient, and provides greater clarity, type-safety, and OOP capability (such that instances may have properties and behaviours) than using integers.

  3. I define enums like

    class Color(object):
    (red, green, blue) = range(3)

    I can use then Color.red wherever I need it.

  4. I think that most of the time enums are a kind of “cheap objects” for the cases where you can’t use real objects because of the lanugage’s or storage limitations. That doesn’t make much sense in Python, where you can just have a normal class and instances of that class, with all the data and methods they need — just like joel wrote.

  5. Fundamentally, an enum is a type whose values are named constants. In Python, creating such a type is pretty trivial, it’s no more complicated than:
    class MyEnum(object):
    pass
    A = MyEnum()
    B = MyEnum()
    if you don’t care about the value of the “constants”; and joel shows how to do it if you do care about the value and behavior of the “constants”. Enumerations in statically-typed languages are generally “closed”, so you can’t add values to the set or change the type’s behavior. Doing that in Python is obviously possible but slightly more complicated.

    Since Python is dynamically typed, the only advantage of language support for Enums would be simplified syntax; I personally don’t think it’s worth it. One could add a metaclass to the standard library that simplifies Enum creation and limits the values, but one would also have to get everyone to agree. The latter is rather simple, the former rather hard.

    • Rather the former is simple and the latter rather hard. My apologies.

  6. This is a great thread. The issues raised here make me wonder if the various flavors of Pythonic enums you guys have proposed illustrate the “curse of abundance” in which Python allows enough fairly easy and good solutions that the people in charge of the language (Guido and whoever else does PEPs) don’t feel a need to bother solving it officially.

    This seemed to me a profound (and yet paradoxical) failure of the Lisp community, where so many people re-created their own special collections and programming constructs that the overall language fractured, and a novice coming to the language wound up having to either recreate all of this stuff for himself, or else pick through a giant morass of half-baked open source options.

Comments are closed.