🔢 Enumerations in Python
Enumerations provide a developer with a restricted set of values.
Introduction
In some scenarios, you want a developer to pick one value from a list. Colors of a traffic light, HTTP methods and HTTP response status codes are all great examples of this type of relationship. To express that relationship in Python, you should use enumerations. Enumerations are a construct that let you define the list of values, and developers pick the specific value they want. Python first supported enumerations in Python 3.4.
Let's suppose I represent the traffic lights as a Python tuple:
What does this tuple communicate to other developers?
- This collection is immutable.
- They can iterate over this collection to get all the lights.
- They can retrieve a specific light through static indexing.
The immutability and retrieval properties are important for my application. I don't want to add or subtract any lights at runtime. Retrieval lets me choose just one light, but it is a bit clunky.
This unfortunately does not communicate intent. Every time a developer sees this, they must remember that 1
means Yellow
. Constantly correlating numbers to lights wastes time. This is fragile and will invariably cause mistakes. To combat this, I'll make aliases for each of these:
That's a bit more code, and still doesn't make it any easier to index into that tuple. Futhermore, there is still a lingering issue in calling code. Consider a function that performs an action based on the light:
Future developers would come across code like this:
Or:
And here lies the crux of the problem. On the happy path, a developer can use the predefined variables. But if somebody accidentally were to use the wrong light, you soon get unwanted behavior. You want to find a way to communicate that you want a very specific, restricted set of values in specific locations.
Enum
Here's an example of Python's enumeration, Enum
, in action:
To access specific instances, you can just do:
If you wanted to print out all the values of the enumeration, you can simply iterate over the enumeration:
Finally, you can communicate your intent in functions that use this Enum
:
This tells all the developers looking ath this function that they should be passing in a TrafficLight
enumeration, and not just any old string. It becomes much harder to introduce typos or incorrect values.
When Not to Use
Enumerations are great for communicating a static set of choices for users. You don't want to use them where your options are determined at runtime, as you lose a lot of their benefits around communicating intent and tooling. If you find yourself in this situation, a dictionary
which offers a natural mapping between two values that can be changed at runtime would be a better choice. You will need to perform membership checks if you need to restrict what values a user an select, though.
Advanced Usage
Automatic Values
For some enumeration, you might want to explicitly specify that you don't care about the value that the enumeration is tied on. This tells users that they should not rely on these values. For this, you can use the auto
function.
By default, auto
will select monotonically increasing values(1, 2, 3...). If you would like to control what values are set, you should implement a _generate_next_value_
function:
Flags
Now that you have the traffic lights represented in an Enum
, we can declare a variable stop_signs
for when the light is Red
or Yellow
, you may track a list of lights as such:
This tells readers that a collection of traffic lights will be unique, and that there might be zero, one, or many lights. This satisfies your needs. But I don't want to rely on every developer remembering to use a set(just one use of a list or dictionary can invite wrong behavior). I want some way to represent a grouping of unique enumeration values and more intuitive. The enum
module gives you a handy base class to use - Flag
:
This lets you perform bitwise operations to combine traffic lights or check if certain lights are present.
Unique
One great feature of enumerations is the ability to alias values. However, there are cases where you want to force uniqueness on the values. Perhaps you are relying on the enumeration to always contain a set number of values, or perhaps it messes with some of the string representations that are shown to customers. No matter the case, if you want to preserve uniqueness in your Enum
, simply add a @unique
decorator.
Integer Conversion
There are two more special case enumerations called IntEnum
and IntFlag
. These map to Enum
and Flag
, respectively, but allow degradation to raw integers for comparison. But I do not recommend using these features, because the situation to use them is when you want compare enumerations and integers directly, this may cause error if the value of an Enum changes in the future.
Summary
Enumerations are simple, and often overlooked as a powerful communication method. Any time that you want to represent a single value from a static collection of values, an enumeration should be your go-to user-defined type. It's easy to define and use them. They offer a wealth of operations, including iteration, bitwise operations and control over uniqueness.