Chapters
Python114 chapters

Classes and objectsChapter 68 of 114

Enums

A fixed set of named values, instead of loose strings scattered about.

The problem with loose strings

Python
def move(direction):
    if direction == "north":
        return "going up"
    return "unknown"

print(move("north"))
print(move("North"))

Output

going up
unknown

A typo or a stray capital gives a wrong answer with no error. Nothing lists the valid values, so nothing can check them.

An enum names them

Python
from enum import Enum

class Direction(Enum):
    NORTH = "north"
    SOUTH = "south"

print(Direction.NORTH)
print(Direction.NORTH.name)
print(Direction.NORTH.value)

Output

Direction.NORTH
NORTH
north

Direction.NORHT is an AttributeError at the moment you write it, rather than a wrong answer much later.

Members are singletons

Compare with is. Each member exists exactly once:

Python
from enum import Enum

class Direction(Enum):
    NORTH = "north"
    SOUTH = "south"

a = Direction.NORTH
b = Direction("north")

print(a is b)
print(a == Direction.SOUTH)
print(Direction["NORTH"] is a)

Output

True
False
True

Look one up by value with Direction(value), and by name with Direction["NAME"]. An unknown value raises:

Python
from enum import Enum

class Direction(Enum):
    NORTH = "north"

try:
    Direction("up")
except ValueError as problem:
    print("ValueError:", problem)

Output

ValueError: 'up' is not a valid Direction

That is validation you did not have to write.

Iterating and membership

Python
from enum import Enum

class Direction(Enum):
    NORTH = "north"
    SOUTH = "south"
    EAST = "east"

print([d.name for d in Direction])
print(len(Direction))
print(Direction.NORTH in Direction)

Output

['NORTH', 'SOUTH', 'EAST']
3
True

The list of valid values is now part of the type, so a form dropdown or a validation message can be generated from it.

auto, when the value does not matter

Python
from enum import Enum, auto

class Status(Enum):
    PENDING = auto()
    ACTIVE = auto()
    DONE = auto()

print(Status.ACTIVE.value)
print([s.name for s in Status])

Output

2
['PENDING', 'ACTIVE', 'DONE']

Use auto() when nothing outside your program sees the value. Give explicit values when they are stored in a database or sent over an API, so a reordering cannot change their meaning.

StrEnum and IntEnum

A plain Enum member is not equal to its value. When it has to interoperate with plain strings, use StrEnum:

Python
from enum import Enum, StrEnum

class Loose(Enum):
    NORTH = "north"

class Tight(StrEnum):
    NORTH = "north"

print(Loose.NORTH == "north")
print(Tight.NORTH == "north")
print(Tight.NORTH.upper())

Output

False
True
NORTH

StrEnum members are strings, so they slot into existing code that expects one. IntEnum does the same for numbers.

With match

Pattern matching and enums fit together neatly:

Python
from enum import Enum

class Status(Enum):
    PENDING = "pending"
    DONE = "done"

def describe(status):
    match status:
        case Status.PENDING:
            return "not started"
        case Status.DONE:
            return "finished"

print(describe(Status.PENDING))
print(describe(Status.DONE))

Output

not started
finished

Note this is a dotted name, so it compares rather than binding — the one case where match does what you would assume.

Test yourself

2 questions

What is the advantage of an enum over loose strings?

Show the answer

A typo is an AttributeError where you wrote it, not a wrong answer later — You also get the list of valid values as part of the type, which nothing can drift out of sync with.

How does StrEnum differ from Enum?

Show the answer

Its members are strings, so they equal their value — That makes it drop into existing code that expects a plain string. A plain Enum member is not equal to its value.

Next chapter

Magic Methods

The dunder methods that make your class work with Python's own syntax.