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?
CA1069: Enums should not have duplicate values.
Enumerations make for clearer and more readable code, particularly when meaningful names are used. The benefits of using enumerations include: Reduces errors caused by transposing or mistyping numbers. Makes it easy to change values in the future.
This module defines four enumeration classes that can be used to define unique sets of names and values: Enum , IntEnum , Flag , and IntFlag . It also defines one decorator, unique() , and one helper, auto . Base class for creating enumerated constants.
Python enums are useful to represent data that represent a finite set of states such as days of the week, months of the year, etc. They were added to Python 3.4 via PEP 435. However, it is available all the way back to 2.4 via pypy. As such, you can expect them to be a staple as you explore Python programming.
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.
>>> 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.
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.
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>)]))
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
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).
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.
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.
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).
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.
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With