Python Power Tip: Enumerated Types | by Don Cross | Towards Data Science

Often, programmers wish to represent something in their code whose value must belong to a predetermined, finite collection of values. Examples include:
Days of the week: Monday, Tuesday, ..., Sunday.
Types of image file format: JPEG, PNG, GIF, ....
The suits in a card game: club, diamond, heart, spade.
The states of some finite state machine.
To address cases like these, many programming languages support enumerated types. Python did not support enumerated types until March 2014, when version 3.4 was released. Before then, Python programmers had to resort to some less-than-ideal tactics.
A common workaround was to define some global variables with unique integer values, like this:
Then the rest of the code could use CLUB, DIAMOND, HEART, or SPADE to represent the suit of a card as needed.
There are problems with this approach:
These global variables are mutable. One has to make sure no code ever assigns a different value to them.
There is no type checking. It is easy to mix up some other integer value with a card suit, causing bugs.
This deficiency of Python led to the creation of various third-party packages. There was a rejected proposal in 2005 to add enumerated types to the Python standard library. Then, in January 2013, an email conversation among language developers reignited consideration of standard support. The result was PEP 435, a proposal to add a new enum module to the standard library. Guido van Rossum, the creator of Python, approved PEP 435 on May 10, 2013. The enum module first appeared in Python 3.4 the following year.
For simple cases where you just want some alternative values, you can follow this pattern:
This creates an enumerated type Suit that defines symbols for the four card suits, called members. The optional decorator @unique adds a sanity check ensuring that all members have unique values. If you accidentally add more than one symbol with the same value, a ValueError exception will occur when the class declaration executes. For example, the following code will fail:
Running this code produces the following behavior:
Traceback (most recent call last):  File "cards2.py", line 2, in <module>    class Suit(Enum):  File "/usr/lib/python3.5/enum.py", line 573, in unique    (enumeration, alias_details))ValueError: duplicate values found in <enum 'Suit'>: Diamond -> Club
At this point, you can quickly spot the problem that Diamond and Club both have the same value: 1.
The members of an enumerated type like Suit are constants. Here are examples of things you can try with the Python interpreter, after repairing the uniqueness problem in Suit. First, if you evaluate one of the members, you see its representation:
>>> s = Suit.Spade>>> s<Suit.Spade: 4>
Each enumeration member also has a name property that results in the name as a string:
>>> s.name'Spade'
Each member also has a value property that returns whatever value you gave it:
>>> s.value4
The name and value properties are read-only. This solves the mutability problem we mentioned above:
>>> s.value = 7Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "/usr/lib/python3.5/types.py", line 141, in __set__    raise AttributeError("can't set attribute")AttributeError: can't set attribute>>> s.name = 'Joker'Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "/usr/lib/python3.5/types.py", line 141, in __set__    raise AttributeError("can't set attribute")AttributeError: can't set attribute>>> Suit.Heart.value = 9Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "/usr/lib/python3.5/types.py", line 141, in __set__    raise AttributeError("can't set attribute")AttributeError: can't set attribute
The enumeration members are directly comparable with each other for equality or inequality:
>>> s == Suit.SpadeTrue>>> s == Suit.HeartFalse>>> s != Suit.HeartTrue
This allows you to write code that handles alternative cases, like this:
Enumeration members work as dictionary keys:
>>> color = { Suit.Heart: 'red', Suit.Diamond: 'red',... Suit.Spade: 'black', Suit.Club: 'black' }>>> color[Suit.Club]'black'
If you have the name of an enumeration member in a string, you can use the type name with brackets to fetch the corresponding enumeration member.
>>> text = 'Heart'>>> Suit[text]<Suit.Heart: 3>
The type name also acts as a function that, when given one of the initializer values as an argument, returns the corresponding member.
>>> Suit(3)<Suit.Heart: 3>
Just by their name, it makes sense that enumerated types should provide the ability to enumerate the members they contain. And indeed that is the case. The type name serves as an enumerator of its members:
>>> for x in Suit:...     print(x)... Suit.ClubSuit.DiamondSuit.HeartSuit.Spade>>> list(Suit)[<Suit.Club: 1>, <Suit.Diamond: 2>, <Suit.Heart: 3>, <Suit.Spade: 4>]
One of the problems I mentioned above is that it is possible to mix up different kinds of enumeration values. Let’s add another enumeration type and see what happens when we try to compare them.
Even though Suit.Club and Animal.Dog have the same associated value, 1, they do not compare as equal:
>>> Suit.Club == Animal.DogFalse
This makes sense, because Suit and Animal are different types, and different types compare as unequal in general:
>>> Suit.Club == 1False>>> Suit.Club == 'one'False
This helps you avoid writing code that confuses an animal with a card suit!
The Enum base class as demonstrated above is typically the most useful way to create an enumerated type. However, there are times where you want enumeration members that act like integer constants. In cases like this, you might not want to make an abstract type so much as a collection of symbols that represent specific integer constants. Perhaps you have an external API that reports the format of an image file as 1=PNG, 2=JPEG, or 3=GIF.
In cases like these, the Python enum module provides another base class called IntEnum. Here is how you could define a less picky enumerated type where the members act like integer constants:
Now the member ImageFormat.JPEG still has its own identity as an enumeration member:
>>> ImageFormat.JPEG<ImageFormat.JPEG: 2>
Yet when compared with the integer 2, it matches:
>>> ImageFormat.JPEG == 2True
You can even do math with it, unlike a member of an Enum:
>>> ImageFormat.JPEG + 13>>> Suit.Club + 1Traceback (most recent call last):  File "<stdin>", line 1, in <module>TypeError: unsupported operand type(s) for +: 'Suit' and 'int'
Other helpful Enum functionality still works, such as enumeration and fetching members using names or values:
>>> list(ImageFormat)[<ImageFormat.PNG: 1>, <ImageFormat.JPEG: 2>, <ImageFormat.GIF: 3>]>>> ImageFormat(3)<ImageFormat.GIF: 3>>>> ImageFormat['PNG']<ImageFormat.PNG: 1>
We have covered the basics of Python enumerated types here, and it’s enough to get you started. Now you can put enumerated types to good use in your code.
However, there are a lot more details and nuances remaining. I encourage you to read the official proposal and documentation pages linked below. Both pages are great reading for a deep understanding of this important language feature.
PEP 435: Adding an Enum type to the Python standard library : this is the proposal that led to adding the enum module to the Python 3.4+ standard library.Python official documentation for the enum module.
PEP 435: Adding an Enum type to the Python standard library : this is the proposal that led to adding the enum module to the Python 3.4+ standard library.
Python official documentation for the enum module.