Enums
Model a fixed set of choices with Enum, IntEnum and StrEnum, look members up by name and value, and branch on them with match.
What you will learn
- Define an Enum and read the name and value of its members
- Look up a member by value and by name, and iterate over all members
- Choose between Enum, IntEnum and StrEnum, and explain how they compare to plain values
- Use auto() for values, add methods to an enum, and match on its members
On this page
Programs are full of values that can only be one of a few things: a traffic light colour, an order status, a log level. Plain strings and numbers work until someone misspells one:
def paint(color): if color == "red": return "stop" return "unknown colour"
print(paint("red"))print(paint("Red")) # same idea, different spellingstopunknown colourPython does not know that "Red" was meant to be "red", so the function quietly returned the wrong answer. An enumeration gives each choice a name and puts the full list in one place. A misspelled member name fails immediately instead of travelling through the program.
Defining an enum
Subclass Enum, and list the members as class attributes:
from enum import Enum
class Color(Enum): RED = 1 GREEN = 2 BLUE = 3
print(Color.RED)print(repr(Color.RED))print(Color.RED.name, Color.RED.value)Color.RED<Color.RED: 1>RED 1Color.RED is a member: a single, unique object with two attributes, name (the text 'RED') and value (the 1 you assigned). The repr shows both. Member names are written in UPPER_CASE, like constants.
Looking members up
Call the enum with a value to find the member that has it. Index it with a name to find the member by name. Iterate over it to visit every member in definition order:
from enum import Enum
class Color(Enum): RED = 1 GREEN = 2 BLUE = 3
print(Color(2))print(Color["BLUE"])for color in Color: print(color.name, color.value)print(Color.RED is Color.RED, Color.RED == Color.GREEN)Color.GREENColor.BLUERED 1GREEN 2BLUE 3True FalseEach member exists exactly once, so is works, and so does ==. Use whichever reads better. Comparing with is makes the “single object” idea explicit.
An invalid lookup raises an error of its own. By value it is a ValueError, and by name it is a KeyError:
from enum import Enum
class Color(Enum): RED = 1 GREEN = 2
try: Color(5)except ValueError as error: print("ValueError:", error)try: Color["PURPLE"]except KeyError as error: print("KeyError:", error)ValueError: 5 is not a valid ColorKeyError: 'PURPLE'Letting Python choose the values: auto()
Often the values do not matter, and you only need the members to be distinct. auto() numbers them from 1:
from enum import Enum, auto
class Direction(Enum): NORTH = auto() EAST = auto() SOUTH = auto() WEST = auto()
print([d.value for d in Direction])[1, 2, 3, 4]Members are not their values
A plain Enum member is a separate kind of object from the number it carries. It is deliberately not equal to it:
from enum import Enum
class Color(Enum): RED = 1
print(Color.RED == 1)FalseThat strictness protects you: a Color can never be mistaken for a quantity. It also means that when you read input from a file or from a user, you must convert it with Color(value). The conversion doubles as validation, because an unknown value raises ValueError.
IntEnum: members that are also integers
When the members must behave like integers, for example because they are compared with numbers or stored in a protocol, use IntEnum:
from enum import IntEnum
class Priority(IntEnum): LOW = 1 MEDIUM = 2 HIGH = 3
print(Priority.HIGH == 3)print(Priority.HIGH > Priority.LOW)print(sorted([Priority.HIGH, Priority.LOW, Priority.MEDIUM]))print(Priority.MEDIUM + 1)TrueTrue[<Priority.LOW: 1>, <Priority.MEDIUM: 2>, <Priority.HIGH: 3>]3An IntEnum member equals its integer, sorts as an integer, and takes part in arithmetic. That convenience has a cost. The member can now be compared with any unrelated integer, which loses the protection of the plain Enum. Reach for IntEnum only when you need the integer behaviour.
StrEnum
StrEnum is the same idea for strings. Requires version 3.11 or later Its members are strings, so they compare with text, work in f-strings, and have all the string methods. With auto(), the value is the lower-case member name:
from enum import StrEnum, auto
class Status(StrEnum): PENDING = auto() DONE = auto()
print(Status.PENDING == "pending")print(Status("done"))print(f"status is {Status.DONE}")print(Status.DONE.upper())Truedonestatus is doneDONEStrEnum is a good fit for statuses and options that travel as text, such as in JSON, a database column or a command-line flag. Python versions before 3.11 do not have it. There you would write class Status(str, Enum), which behaves similarly but formats differently in f-strings.
Adding behaviour
An enum is a class, so it can have methods, properties and class methods. The members are the instances:
from enum import Enum
class Weekday(Enum): MON = 1 TUE = 2 SAT = 6 SUN = 7
@property def is_weekend(self): return self in (Weekday.SAT, Weekday.SUN)
@classmethod def from_label(cls, label): return cls[label.upper()]
print(Weekday.SAT.is_weekend, Weekday.MON.is_weekend)print(Weekday.from_label("tue"))True FalseWeekday.TUEInside the methods, self is a member, which is why self in (Weekday.SAT, Weekday.SUN) works. The class method from_label uses cls[...], the by-name lookup from above.
Branching with match
Requires version 3.10 or later Enum members work well with match. Write them as dotted names, Light.RED, so Python compares against the member:
from enum import Enum
class Light(Enum): RED = "red" YELLOW = "yellow" GREEN = "green"
def action(light): match light: case Light.RED: return "stop" case Light.YELLOW: return "slow down" case Light.GREEN: return "go"
for light in Light: print(light.name, "->", action(light))RED -> stopYELLOW -> slow downGREEN -> goIf you later add a fourth member and forget to handle it, a type checker can warn you that the match is not exhaustive. That check is one of the main reasons to use an enum over strings.
Common pitfalls
A bare name in a case. case RED: is a capture pattern. It matches anything and binds the name RED, rather than testing against a member. Always write the dotted form Light.RED.
Duplicate values make aliases. Two names with the same value do not create two members. The second is another name for the first:
from enum import Enum
class Status(Enum): OK = 1 SUCCESS = 1 # same value: an alias, not a new member FAILED = 2
print(Status.SUCCESS is Status.OK)print(list(Status))True[<Status.OK: 1>, <Status.FAILED: 2>]SUCCESS is OK, and the list of members contains only two entries. If you want duplicates to be an error, decorate the enum with enum.unique.
Reassigning a member. Members are constants. Color.RED = 5 is an error:
from enum import Enum
class Color(Enum): RED = 1
try: Color.RED = 5except AttributeError: print("AttributeError: members cannot be reassigned")AttributeError: members cannot be reassignedComparing a plain Enum with its value. Color.RED == 1 is False, as shown earlier. Compare members with members, or convert the incoming value first.
Using IntEnum by habit. Its members are equal to bare integers and to members of every other IntEnum. For a closed set of choices that should not mix with numbers, a plain Enum is safer.
Write an enum Light with members RED, GREEN and YELLOW, with the lower-case names as values. Give it a method next() that returns the next light in the cycle red, green, yellow, red. Give it a property can_go that is true only for green.
Start with Light.RED and run four steps. On each, print the step number, the member name and go or wait, then move to the next light. Expected output:
0 RED wait1 GREEN go2 YELLOW wait3 RED waitShow solution
from enum import Enum
class Light(Enum): RED = "red" GREEN = "green" YELLOW = "yellow"
def next(self): order = { Light.RED: Light.GREEN, Light.GREEN: Light.YELLOW, Light.YELLOW: Light.RED, } return order[self]
@property def can_go(self): return self is Light.GREEN
light = Light.REDfor step in range(4): print(step, light.name, "go" if light.can_go else "wait") light = light.next()0 RED wait1 GREEN go2 YELLOW wait3 RED waitThe dictionary inside next maps each member to its successor, which keeps the cycle in one readable place. The lesson used match for a similar job, and either works. can_go uses is to compare with the single GREEN member.
Requires version 3.11 or later Write a StrEnum called Level with members DEBUG, INFO and ERROR, whose values are the lower-case words. Then write parse_level(text). It strips spaces, lowers the case, and returns the matching member. For unknown text it raises ValueError with the message unknown level: 'loud' (with the real text, using !r), and it must hide the original enum error with from None.
Run it on "INFO", " error " and "loud". For each good one print the member name and value, for the bad one print error: and the message. Expected output:
INFO infoERROR errorerror: unknown level: 'loud'Show solution
from enum import StrEnum
class Level(StrEnum): DEBUG = "debug" INFO = "info" ERROR = "error"
def parse_level(text): try: return Level(text.strip().lower()) except ValueError: raise ValueError(f"unknown level: {text!r}") from None
for text in ["INFO", " error ", "loud"]: try: level = parse_level(text) print(level.name, level.value) except ValueError as error: print("error:", error)INFO infoERROR errorerror: unknown level: 'loud'Level(...) raises ValueError for an unknown value. The function converts it to a message in the program’s own words, and from None removes the confusing “during handling of the above exception” chain, as the earlier lesson on chaining explained.
Plain is an ordinary Enum and Number is an IntEnum, and both have a member with the value 1. Write down the four printed lines before you run this, and explain the third one.
from enum import Enum, IntEnum
class Plain(Enum): ONE = 1
class Number(IntEnum): ONE = 1
print(Plain.ONE == 1)print(Number.ONE == 1)print(Number.ONE == Plain.ONE)print(Number.ONE + 1)Show solution
FalseTrueFalse2A plain Enum member is not equal to its value, so the first line is False. An IntEnum member is an integer, so the second is True and the arithmetic in the fourth line gives the plain integer 2. The third compares an IntEnum member with a plain Enum member. The Plain member is not an integer and does not know how to equal Number.ONE, so the answer is False.
Summary
- An
Enumis a fixed set of named members. Each has anameand avalue, and each exists exactly once. Color(1)looks up by value (ValueErrorif missing),Color["RED"]looks up by name (KeyErrorif missing), and iterating yields every member.- Plain enum members do not equal their values.
IntEnummembers are integers.StrEnumRequires version 3.11 or later members are strings. auto()picks distinct values for you. Enums can have methods, properties and class methods.- In
match, write members as dotted names. Duplicate values create aliases, not new members.