Featured

Enumerations and Flags in Python Programming

In this post, we’ll describe enumerations, another interesting data type that Python makes available to the developer as part of the standard library.

 

This is not a fixed, defined data type, but rather an approach to defining your own task-specific data types. In the context of object-oriented programming, we’ll discuss in depth how to create custom data types with any functionality. At this point, we'll jump ahead a bit and tackle creating our own enumeration types.

 

Enumeration Types: enum

An enumeration type is a data type whose value range is a set of symbolic constants. An example of this is the (still imaginary) data type weekday, whose instances each represent one day of the week. With the means described up to this point, this data type can be implemented as an integer or a string, for example. Both variants assume an implicitly defined assignment of numbers or character strings to the abstract "day of the week" concept. But this kind of assignment poses some problems:

  • The operators of the int and str data types are still available, but they lose their meaning. For example, it’s not clear what "Monday divided by Tuesday" should mean.
  • There’s no check of whether the value assigned to a weekday variable is valid.
  • The value of a weekday variable can be compared to other int or str instances without a weekday meaning. Possibly worse, day of the week variables can be compared to values representing instances of other enumerations, such as traffic light colors.
  • For screen outputs, the day of the week isn’t output, but the internally assigned value— in the worst case, a number without any reference to the parent concept. This makes it difficult to understand debug output, for example.

The enum module from the standard library provides the basic enum class for immutable enumeration types. In the following example, the already mentioned weekday data type is implemented as an enumeration type:

 

>>> import enum

>>> class Weekday(enum.Enum):

...    Monday = 1

...    Tuesday = 2

...    Wednesday = 3

...    Thursday = 4

...    Friday = 5

...    Saturday = 6

...    Sunday = 7

...

 

For each day of the week, a symbolic constant is created in the weekday class derived from enum. Each constant is assigned an internal value. This value is an integer in this case, but it doesn’t have to be.

 

A concrete day of the week can now be created using the defined constant, the assigned internal value, or its name:

 

>>> Weekday.Saturday

<Weekday.Saturday: 6>

>>> Weekday(6)

<Weekday.Saturday: 6>

>>> Weekday["Saturday"]

<Weekday.Saturday: 6>

 

You can also see from these examples that the screen output of an enumeration value contains its symbolic name. This name can be accessed via the name attribute:

 

>>> Weekday.Saturday.name

'Saturday'

 

Enumeration values can only be compared with each other. In particular, comparison with the internal value evaluates to False:

 

>>> Weekday.Monday != Weekday.Tuesday

True

>>> Weekday.Monday == 1

False

 

Enumeration types allow for running through their values. Any aliases will be skipped (aliases are enumeration values that are assigned the same internal value as a previously defined enumeration value):

 

>>> for day in Weekday:

... print(day)

...

Weekday.Monday

Weekday.Tuesday

Weekday.Wednesday

Weekday.Thursday

Weekday.Friday

Weekday.Saturday

Weekday.Sunday

 

Since Python 3.6, the auto function is available, which can be used to define an enumeration type without assigning concrete internal values:

 

>>> class Weekday(enum.Enum):

...    Monday = enum.auto()

...    Tuesday = enum.auto()

...    Wednesday = enum.auto()

...    Thursday = enum.auto()

...    Friday = enum.auto()

...    Saturday = enum.auto()

...    Sunday = enum.auto()

...

>>> Weekday.Monday

<Weekday.Monday: 1>

 

 

Enumeration Types for Bit Patterns: Flag

Python 3.6 introduced the Flag enumeration type, which defines the internal values so that the symbolic constants can be combined using bitwise operators. This is illustrated by the following example, in which the state of a traffic light system is represented by a combination of symbolic constants:

 

>>> class TrafficLight(enum.Flag):

... Red = enum.auto()

... Amber = enum.auto()

... Green = enum.auto()

...

 

With the help of the bitwise OR, two symbolic constants, also referred to as flags, can be combined:

 

>>> combined_state = TrafficLight.Red | TrafficLight.Amber

 

In our example, combined_state would describe a traffic light where the red and amber lights are currently on together.

 

The bitwise AND can be used to check whether a symbolic constant is included in a combination:

 

>>> combined_state & TrafficLight.Red

<TrafficLight.Red: 1>

>>> combined_state & TrafficLight.Green

<TrafficLight: 0>

 

The result of the bitwise AND can be interpreted as a truth value:

 

>>> bool(combined_state & TrafficLight.Red)

True

>>> bool(combined_state & TrafficLight.Green)

False

 

Integer Enumeration Types: IntEnum

One advantage of the enum enumeration type is that the symbolic enumeration values are strictly separated from their internal representations. For this reason, the comparison between the symbolic value Weekday.Monday and the numeric value 1 always evaluates to False, even though the value 1 is the internal representation of Weekday.Monday in the enumeration.

 

Occasionally, however, this kind of comparability is explicitly desired. For this purpose, the enum module contains the IntEnum data type, which can be used in a similar way to enum. Enumeration values of an enumeration type that inherits from IntEnum can be used as if they were integers:

 

>>> class Weekday(enum.IntEnum):

...    Monday = 1

...    Tuesday = 2

...    Wednesday = 3

...    Thursday = 4

...    Friday = 5

...    Saturday = 6

...    Sunday = 7

...

>>> Weekday.Monday < 10

True

>>> Weekday.Monday * 2

2

>>> Weekday.Monday + Weekday.Tuesday

3

 

In particular, comparisons and operations with values of different IntEnum enumerations are also permitted.

 

Conclusion

Enumerations give your code a vocabulary of its own: a variable holding Weekday.Saturday tells you exactly what it represents, both in your logic and in your debug output. When choosing a type, start with Enum, move to Flag when values need to be combined, and save IntEnum for cases where members have to behave like integers, such as passing values to an older API that expects numbers. That last option gives up some of the safety that makes enumerations useful, so use it deliberately. The class-based approach you used here will come back when you start building fully custom data types of your own.

 

Editor’s note: This post has been adapted from a section of the book Python 3: The Comprehensive Guide by Johannes Ernesti and Peter Kaiser. Johannes is a research scientist at DeepL. He is a graduate mathematician and received his doctorate in applied mathematics from the Karlsruhe Institute of Technology (KIT). Peter is a research scientist at DeepL. He has a degree in computer science and received a doctorate in humanoid robotics from the Karlsruhe Institute of Technology (KIT).

 

This post was originally published 9/2022.

Recommendation

Your All-in-One Guide to Python!
Your All-in-One Guide to Python!

This award-winning guide covers everything you need to write effective Python — from core concepts like functions, data types, and object orientation to advanced topics like GUIs, Django, network programming, and scientific computing. Whether you're picking up Python for the first time or leveling up your skills, it's a hands-on reference you'll keep coming back to.

Learn More
Rheinwerk Computing
by Rheinwerk Computing

Rheinwerk Computing is an imprint of Rheinwerk Publishing and publishes books by leading experts in the fields of programming, administration, security, analytics, and more.

Comments