132

In Python 3.4, we got an Enum lib in the standard library: enum. We can get a backport for enum that works with Python 2.4 to 2.7 (and even 3.1 to 3.3), enum34 in pypi.

But we've managed to get along for quite some time without this new module - so why do we now have it?

I have a general idea about the purpose of enums from other languages. In Python, it has been common to use a bare class as follows and refer to this as an enum:

class Colors:
    blue = 1
    green = 2
    red = 3

This can be used in an API to create a canonical representation of the value, e.g.:

function_of_color(Colors.green)

If this has any criticisms, it's mutable, you can't iterate over it (easily), and how are we to know the semantics of the integer, 2?

Then I suppose I could just use something like a namedtuple, which would be immutable?

>>> Colors = namedtuple('Colors', 'blue green red')
>>> colors = Colors('blue', 'green', 'red')
>>> colors
Colors(blue='blue', green='green', red='red')
>>> list(colors)
['blue', 'green', 'red']
>>> len(colors)
3
>>> colors.blue
'blue'
>>> colors.index(colors.blue)
0

The creation of the namedtuple is a little redundant (we have to write each name twice), and so somewhat inelegant. Getting the "number" of the color is also a little inelegant (we have to write colors twice). Value checking will have to be done with strings, which will be a little less efficient.

So back to enums.

What's the purpose of enums? What value do they create for the language? When should I use them and when should I avoid them?

Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
  • Hm, so when is it better to use `namedtuple` then? I have a case when I need a constant, say, collection of strings representation, `RANKING_CRITERIA = ['count', 'pair_count', 'freq']`, and I don't want to refer by number IDs bc it's not understandable in the code, but the dictionary is not an option bc it's mutable and the order of `.keys()` is not guaranteed. Seems like `namedtuple` is my case then. Any other ideas? – alisa Jun 18 '20 at 19:29
  • @alisa when to use namedtuples? Use them when you need immutable records and don't have much of a need for inheritance. Dicts are now ordered by default, though, so `.keys()` will be ordered by insertion. More context would help me help you. What are you trying to do, exactly, and why are you doing it? It may be that a list of strings is most apropos. – Russia Must Remove Putin Jun 18 '20 at 20:27

1 Answers1

236

What's the purpose of enums? What value do they create for the language? When should I use them and when should I avoid them?

The Enum type got into Python via PEP 435. The reasoning given is:

The properties of an enumeration are useful for defining an immutable, related set of constant values that may or may not have a semantic meaning.

When using numbers and strings for this purpose, they could be characterized as "magic numbers" or "magic strings". Numbers rarely carry with them the semantics, and strings are easily confused (capitalization? spelling? snake or camel-case?)

Days of the week and school letter grades are examples of this kind of collections of values.

Here's an example from the docs:

from enum import Enum

class Color(Enum):
    red = 1
    green = 2
    blue = 3

Like the bare class, this is much more readable and elegant than the namedtuple example, it is also immutable, and it has further benefits as we'll see below.

Strictly dominant: The type of the enum member is the enum

>>> type(Color.red)
<enum 'Color'>
>>> isinstance(Color.green, Color)
True

This allows you to define functionality on the members in the Enum definition. Defining functionality on the values could be accomplished with the other prior methods, but it would be very inelegant.

Improvement: String coercion

The string representation is human readable, while the repr has more information:

>>> print(Color.red)
Color.red
>>> print(repr(Color.red))
<Color.red: 1>

I find this to be an improvement over the magic numbers and even possibly better than strings from the namedtuple.

Iteration (parity):

The enum supports iteration (like the namedtuple, but not so much the bare class) too:

>>> for color in Color:
        print(color)
Color.red
Color.green
Color.blue

The __members__ attribute is an ordered mapping of the names of the enums to their respective enum objects (similar to namedtuple's _asdict() function).

>>> Color.__members__
mappingproxy(OrderedDict([('red', <Color.red: 1>), ('green', <Color.green: 2>), 
('blue', <Color.blue: 3>)]))

Supported by pickle (parity)

You can serialize and deserialize the enum (in case anyone was worried about this):

>>> import pickle
>>> color.red is pickle.loads(pickle.dumps(color.red))
True

Improvement: Aliases

This is a nice feature that the bare class doesn't have, and it would be difficult to tell the alias was there in the namedtuple.

class Color(Enum):
    red = 1
    green = 2
    blue = 3
    really_blue = 3

The alias comes after the canonical name, but they are both the same:

>>> Color.blue is Color.really_blue
True

If aliases should be prohibited to avoid value collisions, use the enum.unique decorator (a strictly dominant feature).

Strictly dominant: comparisons done with is

The enum is intended to be tested with is, which is a fast check for a single object's identity in the process.

>>> Color.red is Color.red
True
>>> Color.red is Color.blue
False
>>> Color.red is not Color.blue
True

Tests for equality work as well, but tests for identity with is are optimal.

Different semantics from other Python classes

Enum classes have different semantics from regular Python types. The values of the Enum are instances of the Enum, and are singletons in memory for those values - there is no other purpose for instantiating them.

>>> Color.red is Color(1)

This is important to keep in mind, perhaps it is a downside, but comparing on this dimension is comparing apples with oranges.

Enums not assumed to be ordered

While the Enum class knows what order the members are created in, enums are not assumed to be ordered. This is a feature because many things that may be enumerated have no natural order, and therefore order would be arbitrary.

However, you can give your enums order (see the next section).

Subclassing

You can't subclass an Enum with members declared, but you can subclass an Enum that doesn't declare members to share behavior (see the OrderedEnum recipe in the docs).

This is a feature - it makes little sense to subclass an Enum with members, but again, the comparison is apples and oranges.

When should I use enum.Enum?

This is the new canonical enumeration in Python. Collaborators will expect your enums to behave like these enums.

Use it anywhere you have a canonical source of enumerated data in your code where you want explicitly specified to use the canonical name, instead of arbitrary data.

For example, if in your code you want users to state that it's not "Green", "green", 2, or "Greene", but Color.green - use the enum.Enum object. It's both explicit and specific.

There are a lot of examples and recipes in the documentation.

When should I avoid them?

Stop rolling your own or letting people guess about magic numbers and strings. Don't avoid them. Embrace them.

However, if your enum members are required to be integers for historic reasons, there's the IntEnum from the same module, which has the same behavior, but is also an integer because it subclasses the builtin int before subclassing Enum. From IntEnum's help:

class IntEnum(builtins.int, Enum)

we can see that the IntEnum values would test as an instance of an int.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
Russia Must Remove Putin
  • 374,368
  • 89
  • 403
  • 331
  • 13
    Appreciate this comprehensive answer greatly. It is seeming to me that one could now replace (entirely?) Enums with Literal types https://www.python.org/dev/peps/pep-0586/ - thoughts on this? – DBCerigo Mar 28 '20 at 10:08
  • I don't see how Enums are replaced with Literal types. Can you connect the dots for me on that? – Russia Must Remove Putin Mar 29 '20 at 18:34
  • 4
    I think I'll probably retract the more sweeping (i.e. "entirely") part of that statement :) But as an example of such a replacement, we were using a lot of `class SomeCollection(str, Enum)` classes, until we found Literals. Having replaced these, we a) think it is simpler to read (strings look like strings now, not an attribute of a class), and b) we can combine them, e.g. `LargerCollection = Literal[SmallCollection1, SmallCollection2]`, which we couldn't do with Enums (this is the most pertinent reason for us tbh). Very much appreciate this is a particular case and not general though. – DBCerigo Mar 31 '20 at 07:48
  • 1
    Taking from your answer; "defining an immutable, related set of constant values that may or may not have a semantic meaning", from you answer. Maybe one could say Literals are "defining an immutable, related set of _allowed_ constant values that may or may not have a semantic meaning"? Thus they are distinctly different, but related - seem reasonable and at all useful? :) – DBCerigo Mar 31 '20 at 07:52
  • 3
    Couldn't find a good discussion of "Enum vs Literal Type" for python specifically, but this post is that for TypeScript which some may still find useful: https://stackoverflow.com/questions/49761972/difference-between-string-enums-and-string-literal-types-in-ts – DBCerigo Mar 31 '20 at 09:15
  • I use Enum and the more I use it, the more I want to get rid of it. Using a custom constants class appears more convenient to me for 2 reasons: 1-With a custom constants class I can simply refer to MyClass.my_field as of for enums in some situations, I might be force to use MyEnum.my_field.value. 2- It is slightly shorter to code a custom constants class than coding an enum. – Greg7000 May 03 '23 at 18:24