Build Enumerations of Constants with Python's Enum

In this tutorial, we will learn to build an enumeration of constants using the Python enum method. We will discuss how to work with the enumerations and their members in Python and customize enumeration classes with new functionalities.

Introduction to Enumeration in Python

There are many other programming languages such as Java, C++ that include the syntax which supports the enumeration or enum. This data type permits to create semantically related constants that we can access through the enumeration itself. However, Python doesn't have syntax for enum dedicatedly. But Python standard library provide the enum module which helps to work with the enumeration using the Enum class.

Several examples of enumeration include days of the week, months and seasons of the year, Earth's cardinal directions, a program's status codes, HTTP status codes, colors in a traffic light, and pricing plans of a web service. In other words, a limited set of possible values can refer as Enum.

According to PEP 435 -

An enumeration is a set of symbolic names bound to unique, constant values. Within an enumeration, the values can be compared by identity, and the enumeration itself can be iterated over.

We can also create some similar to an enumeration without using Enum class by defining the sequence of similar or related constants. It can be done as follows -

One issue with using this idiom to group many related constants is that it needs to scale better. Additionally, the first constant will have a value of 0, which is falsy in Python. It can be problematic in certain situations, particularly those involving Boolean tests. To overcome these limitations, an alternative approach can be used. One such approach is to define a class with class-level constants. Using a class, we can group related constants in a scalable manner. Furthermore, we can use a unique name for each constant, which avoids the issue of having a false value as the first constant. Here's an example:

Example -

In many cases, using enumerations can be a better alternative to the above idiom as it can help you avoid the drawbacks and produce more organized, readable, and robust code. Enumerations have several benefits, some of which relate to ease of coding. For example:

  • Enumerations allow us to define a named set of related constants, which makes your code more organized and readable. It is because it's clear that these constants are related, and we don't have to search for all the places where they're used.
  • It makes our code more robust because we can define a fixed set of values that are allowed. This means that if someone tries to use an invalid value, you can catch it early on, rather than having to debug issues later.
  • Enumerations can help prevent hard-to-find errors caused by typos or other mistakes. Since the enumeration defines a fixed set of values, we can use them without worrying about spelling errors or using the wrong value.
  • Enumerations are often easier to read and understand than magic numbers or other constants, which can be unclear or ambiguous.

Using enumerations can make your code more organized, readable, and robust, while also making it easier to write and debug.

Once you have a basic understanding of enumerations in programming and in Python, you can use Python's Enum class to define your own enumeration types. The Enum class is a built-in Python class that allows you to define an enumeration with named constants.

Creating Enumeration with Python's Enum

Python's enum module includes the Enum class, which provides an easy way to create enumeration types. We can create your own enumerations using either the subclassing method or the functional API provided by the Enum class.

Subclassing the Enum class allows us to define our own set of related constants as enum members within the class definition. Alternatively, we can use the functional API to create an enumeration by passing in the name of the enumeration and its members as arguments to the Enum() function.

The Python enum module provides a general-purpose enumeration type that comes with iteration and comparison capabilities. With this type, you can define a set of named constants that can replace literals of common data types, such as numbers and strings.

By using this type, you can improve the readability and maintainability of your code by giving meaningful names to constants instead of using literals throughout your code. This makes your code easier to understand, and also reduces the risk of errors caused by typos or incorrect values.

Example -

Output:

[, , , , , , , , , , , ]

Explanation -

In the above example, we define an Enum class named Month with twelve members, each representing a month of the year. Each member is assigned a corresponding integer value from 1 to 12.

To use this enumeration, you can simply refer to its members by name. For example, Month.JANUARY represents the first month of the year.

Overall, creating an Enum for month names can help improve the readability and maintainability of our code by using meaningful names instead of literals throughout your code.

If we attempt to modify the value of an enumeration member, an AttributeError will be raised. It's important to note that while the member names within an enumeration are constants and cannot be modified, the name of the enumeration itself is a variable and can be rebound at any point during program execution. However, it's generally not recommended to rebind the name of an enumeration, as doing so can cause confusion and make the code harder to understand. It's typically better to use the original name of the enumeration to refer to its members consistently throughout the program.

The enum can be considered as the collection of constants such as list, tuples, or dictionaries, and also iterable. That's why we can use list() to turn an enumeration into a list of enumeration members.

Example -

Output:



We can also use the idioms based on range() to build enumerations.

Example -

Output:

[, , , ]

The Enum class's syntax is similar to regular Python class but they are special classes that different from the normal Python classes. The main difference is -

  • Enum members are constants and cannot be modified at runtime.
  • Enum members are unique and cannot be duplicated within the same enumeration.
  • Enum members are hashable and can be used as keys in dictionaries or elements in sets.
  • Enum classes cannot be subclassed or instantiated directly.

These differences make enum a powerful tool for creating sets of named constants in Python that are organized, readable, and robust. By using enum, you can help prevent bugs and make your code easier to understand and maintain.

While it's common for the members of an enumeration to have consecutive integer values, Python allows the values of enum members to be of any type, including user-defined types. As an example, consider the following enumeration of school grades, where non-consecutive numeric values are used in descending order:

Example -

Explanation -

In this example, we define an Enum class named Grade with six members, each representing a different school grade. Instead of using consecutive integer values, we use a tuple for each member to store the numeric value and a string representing the grade description.

To access the value or description of a member, you can reference the corresponding attribute of the enum member. For example, Grade.A_PLUS.value would return the tuple (95, "Excellent"), while Grade.A_PLUS.value[0] would return the numeric value 95.

Overall, the ability to use non-consecutive values and user-defined types for enum members makes Python enum more flexible and powerful than Enum in some other programming languages.

In Python, we can use string values for your enumeration members. Here's an example of a Size enumeration that could be used in an online store:

Example -

In the above example, we define an Enum class named Size with four members, each representing a different size for an online store product. Instead of using integer values or other types, we use string values for each member.

To use these enum members in code, we can reference them directly using their names, like this:

We can create the enumeration class of Boolean value which takes only two values.

Example -

The above example shows how we can add additional context to our code. The code gives better readability, emulating a switch object with two possible states.

We can also define heterogeneous values.

Example -

Output:

UserResponse.NO
UserResponse.YES

Using different data types for constants can lead to inconsistency and compromise type safety, so it's generally not recommended. It's best to group related constants of the same data type together in enumerations to ensure consistency and improve type safety.

Creating Enumeration with the Functional API

With the Enum class, we can create enumerations using a functional API instead of the traditional class syntax. Simply call the Enum class with the appropriate arguments, similar to how we would call a function or any other callable.

The functional API used to create Enum classes is similar in structure to the way the namedtuple() factory function works. In the case of Enum, the functional signature follows the following format:

Below is the table that shows each argument's meaning in Enum's signature.

ArgumentDescriptionRequired
valueIt takes the string with the name of the new enumeration class.Yes
namesIt provides the name for the enumeration members.Yes
moduleIt takes the name of the module that defines the enumeration classNo
qualnameIt takes the location of the module that defines the enumeration class.No
typeIt stores a class to be used as the first mixin class.No
startIt accepts the starting value from the enumeration value will begin.No

The module and qualname are essential if we need pickle and unpickle enumerations.

When creating an Enum class, if the module parameter is not explicitly set, Python will attempt to determine the module automatically. However, if it fails to do so, the resulting class may not be picklable. Similarly, if the qualname parameter is not set, Python will set it to the global scope, which could cause issues when unpickling the enumeration in certain situations. Therefore, setting both module and qualname explicitly when creating an Enum class is generally recommended.

To incorporate additional functionality, such as extended comparison capabilities, into the enumeration, we can pass a mixin class as an argument using the type parameter when creating the Enum. Including a mixin class allows us to provide custom enumeration with additional functionality beyond what is provided by the default Enum class.

The start argument allows customizing the initial value of enumeration members. By default, this argument is set to 1 instead of 0. This default value is used because 0 has a Boolean value of False, whereas enum members evaluate True. Therefore, starting from 0 could be unexpected and lead to confusion, hence the default value of 1.

Example -

Building Enumeration from Automatic Values

The Python enum module provides a handy auto() function that enables automatic value assignment for enum members. By default, this function assigns consecutive integer values to each member, in the order they are defined. Let's understand the following example.

Example -

Output:

[, , , , , , 

To use the auto() function for automatic value assignment, we must call it once for each enum member that requires an automatic value. Additionally, we can combine auto() with concrete values, similar to how Day.WEDNESDAY and Day.SUNDAY were defined in the example.

Creating Enumeration with Aliases and Unique Values

We can create enumerations where two or more members share the same constant value. These duplicated members are called aliases, and they can be useful in certain situations. For instance, consider an enum that represents different operating systems (OS), as shown in the following code example:

Example -

Output:

[, , , , ]

Explanation -

In the above example, the OS enum includes an alias for the WINDOWS member, with the name WINDOWS_ALIAS. Both WINDOWS and WINDOWS_ALIAS share the same value of 'win'. This can be useful in certain situations, such as when multiple names are used to refer to the same OS in different parts of a codebase.

We can also omit the duplicate values in aliases using the @unique decorator from enum module.

Example -

Output:

File "/usr/lib/python3.11/enum.py", line 1564, in unique
raise ValueError('duplicate values found in %r: %s' %
ValueError: duplicate values found in : WINDOWS_ALIAS -> WINDOWS

Comparing Enumeration

Enumerations can be compared in conditional statements such as if...elif and match...case. This suggests that enumeration members can be compared with each other.

By default, enum supports two types of comparison operators:

Identity comparison using is and is not operators, which check if two objects are the same instance.

Value comparison using == and != operators, which check if two objects have the same value.

For example, the following code shows a basic comparison of two enumeration members using the value comparison operator:

Example -

Conclusion

This tutorial included the detailed overview of Enum in Python. Enums are a widely used and popular data type in programming languages that enable developers to organize related constants and access them via the enumeration. This simplifies the coding process by allowing programmers to group related constants together in a single place and use them throughout their program. Although Python doesn't have a dedicated syntax for enums, developers can still use this popular data type in Python through the enum module, which provides the Enum class. This class allows programmers to create and use enums by grouping related constants together under a single namespace.






Latest Courses