DEV Community

loading...

Dynamic generation of informative `Enum`s in Python

Ignacio Vergara Kausel
An experimental physicist in a previous life, currently a software developer.
・2 min read

Since I started learning some Rust and getting to know its enums, I've been very into using Enums in Python. And while enums in both languages are very different, they're close enough for most of my current use cases. Moreover, within Python, Enums are used in frameworks like Typer (CLI) and FastAPI (API), where they are used to provide validation to input within a set of possibilities.

In Python, an Enum without explicit member values is created as follows

from enum import Enum, auto
class Color(Enum):
    RED = auto()
    BLUE = auto()
    GREEN = auto()

list(Color)
# [<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]
Enter fullscreen mode Exit fullscreen mode

or alternatively, using a functional API

from enum import Enum
Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)
list(Animal)
# [<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]
Enter fullscreen mode Exit fullscreen mode

Great! The problem is that when having Enums that interface with external users, the integer values of the members are of little use and very uninformative.

That's why I tend to prefer to use a different way, where the values are strings.

class Animal(str, Enum):
    ANT = 'ant'
    BEE = 'bee'
    CAT = 'cat'
    DOG = 'dog'
list(Animal)
# [<Animal.ANT: 'ant'>,
#  <Animal.BEE: 'bee'>,
#  <Animal.CAT: 'cat'>,
#  <Animal.DOG: 'dog'>]
Enter fullscreen mode Exit fullscreen mode

Now, attending to the real problem at hand hinted at by the title. I had a rather long set of about 15 possible elements, and I didn't want to manually create all the elements of the Enum myself in a way that the values are informative. That's the exact thing we try to avoid, repetitive manual work.
We already saw the functional API work with an iterable, so basically, the problem is already solved. However, it does in a way that the member values are uninformative.

The solution is to provide a dictionary instead of a list (or a string in the example). Then, the (keys, value) pairs of the dictionary become the member name and value of the Enum as shown below. Going from a list (or an iterable) to a dictionary is straightforwardly achieved with a dictionary comprehension.

Animal = Enum('Animal', {el:el.lower() for el in 'ANT BEE CAT DOG'.split(" ")}, type=str)
list(Animal)
# [<Animal.ANT: 'ant'>,
#  <Animal.BEE: 'bee'>,
#  <Animal.CAT: 'cat'>,
#  <Animal.DOG: 'dog'>]
Enter fullscreen mode Exit fullscreen mode

Which accomplishes what I was going for.

Discussion (0)