Python Module 9 – Object-oriented Python and the data model
Enums: Enum, IntEnum, StrEnum and Flag
Define fixed sets of named values with Python enums, look members up by name and value, choose between IntEnum and StrEnum, and combine options with Flag.
What you will learn
- Define an enumeration and read the name and value of its members
- Find members by value and by name, and handle values that are not members
- Choose between Enum, IntEnum, StrEnum and Flag for a task
- Use auto(), @unique and Flag combinations correctly
- Use enum members in match statements and state machines
Before you start
What you’ll learn · Before you start
What you will learn
- Define an enumeration and read the name and value of its members
- Find members by value and by name, and handle values that are not members
- Choose between Enum, IntEnum, StrEnum and Flag for a task
- Use auto(), @unique and Flag combinations correctly
- Use enum members in match statements and state machines
Before you start
On this page
Five order statuses written as strings are five places for a typo: if status == "shiped": is never true, and
nothing reports it. An enumeration (enum): A class whose members form a fixed, named set, such as the statuses of an order. Each member has a name and a value and is created once, when the class is defined; the enum module provides Enum, IntEnum, StrEnum and Flag., or enum, is a class whose objects form a fixed, named
set, such as sizes, channels or permissions. Each enum member: One named value of an enumeration, such as OrderStatus.SHIPPED: an object created once with its class, with a name ("SHIPPED") and a value ("shipped"), that cannot be changed and is found again by value or by name. has a name and a value, is
created once when the class is defined, and is compared with is or ==. Writing OrderStatus.SHIPPED instead of
"shipped" turns a typo into an error at once, and lets editors and type checkers list every allowed value.
The enum module offers four kinds. Enum members are plain objects that never equal raw values. IntEnum members
are also integers, and StrEnum members are also strings, so they compare equal to their values and go into JSON as
they are. Flag members are bits that combine with | into sets of options. auto() chooses values for you, and
@unique refuses two members with the same value. Below you will define an order status, compare the kinds and match
on members.
Each member has a name and a value, and exists once
from enum import Enum
class OrderStatus(Enum):
PLACED = "placed"
PACKED = "packed"
SHIPPED = "shipped"
DELIVERED = "delivered"
CANCELLED = "cancelled"
status = OrderStatus.SHIPPED
print(status, repr(status), status.name, status.value)
print(OrderStatus("packed"), OrderStatus["DELIVERED"]) # by value, by name
print(status is OrderStatus("shipped"), status == "shipped", "shipped" in OrderStatus)
print(len(OrderStatus), [s.name for s in OrderStatus])
for attempt in ("lost", "SHIPPED"):
try:
OrderStatus(attempt)
except ValueError as error:
print("ValueError:", error)
try:
status.value = "returned"
except AttributeError as error:
print("AttributeError:", error) Output
OrderStatus.SHIPPED <OrderStatus.SHIPPED: 'shipped'> SHIPPED shipped OrderStatus.PACKED OrderStatus.DELIVERED True False True 5 ['PLACED', 'PACKED', 'SHIPPED', 'DELIVERED', 'CANCELLED'] ValueError: 'lost' is not a valid OrderStatus ValueError: 'SHIPPED' is not a valid OrderStatus AttributeError: <enum 'Enum'> cannot set attribute 'value'
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 order_status.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Three ways to reach one enum member
Text description of the diagram
The diagram shows three expressions on the left, each with an arrow to the same box on the right.
- OrderStatus.SHIPPED reads the member as an attribute of the class.
- OrderStatus("shipped") looks the member up by its value.
- OrderStatus["SHIPPED"] looks the member up by its name.
All three arrows end at one object: the member SHIPPED, whose name is "SHIPPED" and whose value is "shipped". It was created once, when the class was defined, so members can be compared with is as well as with ==.
OrderStatus.SHIPPEDis a member. Itsnameis the identifier from the class body and itsvaluethe object assigned to it; its repr shows both.OrderStatus("packed")finds a member by value, andOrderStatus["DELIVERED"]by name; the diagram shows all three ways to a member. A value that belongs to no member raisesValueError, which names the enum, and a name lookup with a wrong name raisesKeyError.- Members are singletons, so the lookup returned the very same object, and
isworks. A plain member is never equal to its raw value:status == "shipped"isFalse. Since Python 3.12,"shipped" in OrderStatuschecks the values. - The class is iterable in definition order and has a length, which makes enums handy for menus and validation.
- Members cannot be changed: assigning to
valueraisesAttributeError.
Use an enum whenever a variable may only hold one of a known set of values. Code that compares strings, such as
if status == "shiped":, fails silently on a typo; OrderStatus.SHIPED fails loudly with AttributeError. The set
has to be fixed in the code, though: product categories that shop staff add every week, or the list of countries in
a form, are data, and belong in a database table or a file rather than an enum that needs a new release for every
change.
Checkpoint
Check: Members, names and values
Enum, IntEnum, StrEnum and Flag: what members also are
import json
from enum import Enum, Flag, IntEnum, StrEnum, auto
class Size(Enum):
SMALL = 1
LARGE = 2
class Priority(IntEnum):
LOW = 1
HIGH = 3
class Channel(StrEnum):
EMAIL = auto() # auto() in a StrEnum gives the lower-case name: "email"
SMS = auto()
class Access(Flag):
READ = auto() # 1, 2, 4: one bit each
WRITE = auto()
SHARE = auto()
print("Enum: ", Size.SMALL == 1, f"{Size.SMALL}")
print("IntEnum:", Priority.HIGH == 3, Priority.HIGH + 1, f"{Priority.HIGH}", sorted([Priority.HIGH, 2, Priority.LOW]))
print("StrEnum:", Channel.SMS == "sms", f"{Channel.EMAIL}", Channel.EMAIL.upper())
both = Access.READ | Access.WRITE
print("Flag: ", both, Access.WRITE in both, Access.SHARE in both, list(both))
print(json.dumps({"priority": Priority.HIGH, "channel": Channel.SMS}))
try:
json.dumps({"size": Size.LARGE})
except TypeError as error:
print("TypeError:", error) Output
Enum: False Size.SMALL
IntEnum: True 4 3 [<Priority.LOW: 1>, 2, <Priority.HIGH: 3>]
StrEnum: True email EMAIL
Flag: Access.READ|WRITE True False [<Access.READ: 1>, <Access.WRITE: 2>]
{"priority": 3, "channel": "sms"}
TypeError: Object of type Size is not JSON serializable
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 four_kinds.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The table sums up what the program shows for each kind:
| Criterion | Enum | IntEnum | StrEnum (3.11 and later) | Flag |
|---|---|---|---|---|
| Members are also | Nothing else | int | str | Combinable bits |
| Equal to raw values | No | Yes | Yes | No |
| Printed and formatted as | Size.SMALL | The number | The string | Access.READ|WRITE |
| When to choose | States and choices inside a program | Codes exchanged with C, databases or old APIs | Values exchanged as text: JSON, URLs, settings files | Sets of options that combine |
Enum is the default, because it keeps members apart from everything else. IntEnum sorts and calculates with ints,
which is right for numeric codes and wrong for anything else. StrEnum members format as their values and write
themselves into JSON, and auto() in a StrEnum uses the lower-case member name as the value, so Channel.EMAIL is
"email". Flag members each own a bit flag: One bit of a whole number used as a yes-or-no setting, so that several settings share one number: mode = READ | WRITE switches two on, and mode & WRITE tests one. More in “Type conversion, number bases and bitwise operators”; READ | WRITE is a combination, and in tests
membership of one flag in a combination. A plain Enum member cannot be written to JSON as it is, which the last line
of the example shows.
A quick way to choose: when the members never leave your program, use Enum. When another system already sends
numbers, IntEnum saves a conversion at the boundary, and when it sends text, StrEnum does the same. When one
value must hold several options at once, Flag is the only kind that combines them. The kinds arrived over several
releases, which matters when your code must run on an older Python:
- 3.4
The enum module, with Enum and IntEnum
PEP 435 added the enum module to the standard library. Enum and IntEnum date from this release.
- 3.6
Flag, IntFlag and auto() new kinds
Flag and IntFlag made members that combine with |, and auto() let the class choose the values.
- 3.11
StrEnum, and IntEnum printed as a number new kind
StrEnum arrived, with members that are also strings. In the same release str() of an IntEnum member became the str() of its int, so it prints as the number.
- 3.12
in accepts values changed
"shipped" in OrderStatus checks the values of the members. Before 3.12 a containment check with anything but a member raised TypeError.
The timeline has 4 events, from 3.4 to 3.12.
Checkpoint
Check: Enum, IntEnum, StrEnum and Flag
A case matches a member by its dotted name
from enum import Enum
class OrderStatus(Enum):
PLACED = "placed"
PACKED = "packed"
SHIPPED = "shipped"
DELIVERED = "delivered"
CANCELLED = "cancelled"
def message(status):
match status:
case OrderStatus.PLACED | OrderStatus.PACKED:
return "We are getting your order ready."
case OrderStatus.SHIPPED:
return "Your order is on its way."
case OrderStatus.DELIVERED:
return "Delivered. Enjoy!"
case OrderStatus.CANCELLED:
return "This order was cancelled."
case _:
raise ValueError(f"not an order status: {status!r}")
for status in OrderStatus:
print(f"{status.name:<9} {message(status)}")
try:
message("shipped") # a string is not a member
except ValueError as error:
print("ValueError:", error) Output
PLACED We are getting your order ready. PACKED We are getting your order ready. SHIPPED Your order is on its way. DELIVERED Delivered. Enjoy! CANCELLED This order was cancelled. ValueError: not an order status: 'shipped'
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 match_status.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
In a case, a dotted name such as OrderStatus.SHIPPED is a value pattern: A dotted name in a case, such as case Status.SHIPPED:. Python looks the value up and compares the subject with ==, so it tests a constant without capturing. More in “Pattern matching with match and case”, compared
with ==, and | combines several members into one case. A bare name would not work: case SHIPPED: is a
capture pattern: A bare name in a case, such as case code:, which fits any subject and binds it to that name. It never compares, so case SHIPPED: overwrites a constant instead of testing it. More in “Pattern matching with match and case” that matches anything, and Python refuses it before other cases.
Follow three subjects through the cases:
The drawing shows the whole scene; its 5 steps are listed below.
Every step as text
- PACKED. The loop passes OrderStatus.PACKED. The first case compares it with PLACED, which is not equal, then with PACKED, which is, so message() returns "We are getting your order ready."
- SHIPPED. OrderStatus.SHIPPED fails both comparisons of the first case and fits the second, so the message is "Your order is on its way."
- A plain string. message("shipped") gets a string. A plain Enum member is never equal to a raw value, so "shipped" fits none of the dotted names.
- The last case. case _ fits anything, and its body raises ValueError, so the string cannot slip through as if it were a status.
- A bare name. Writing case SHIPPED without the class name would make a capture pattern, which fits any subject and binds it to a new variable. Python refuses it before other cases, because they could never be reached.
The final case _: raises for anything that is not a member, so a plain string such as "shipped" cannot slip
through as if it were a status. Static type checkers go one step further: they know every member of an enum, so they
can warn when a match handles some statuses and forgets others, which a plain string never allows.
Checkpoint
Check: Matching members in case patterns
Check yourself
Practice that adapts to you
Practise this lesson
10 questions about 8 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Flags, aliases and stored values in depth: three traps reproduced Pro: opens with a Pro, Premium or Ultimate pass
Flags pack several options into one value, and they behave in ways a plain enum never does. Aliases, comparisons
between unrelated integer enums and stored auto() values cause quiet bugs that show up only in production data;
each is reproduced below.
In this part:
- Permission flags: grant, revoke, test and name combinations
- Members that carry data and behaviour
- Three traps: aliases, unrelated IntEnums and auto() values on disk
1 diagram · 3 runnable examples (Python) · about 7 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Enums in real code: a state machine, JSON with old values, and an exercise Pro: opens with a Pro, Premium or Ultimate pass
Enums earn their keep at the edges of a program, where statuses change and values arrive from outside. This part
builds an order state machine with a completeness check, reads and writes enum values as JSON, accepts legacy
spellings with _missing_, and ends with an exercise that combines a state cycle with flags.
In this part:
- An order state machine driven by an enum
- JSON and old spellings, accepted by one hook
- Exercise: a traffic light and permission flags
- A traffic light that refuses bad changes, and permission flags
- Results of the sample tests
3 diagrams · 2 runnable examples (Python) · 1 exercise with sample tests · about 9 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher · Warm-up
Why use an enum instead of plain string constants such as "shipped" for an order's status?
An enum makes the set of allowed values part of the code. With strings, a typo such as "shiped" is just another string: comparisons are quietly false and the bug appears far away. With an enum, OrderStatus.SHIPED raises AttributeError at once, and OrderStatus("shiped") raises ValueError when outside data arrives. The class also lists every allowed value, so code can iterate over them, editors can complete them and type checkers can tell when a match statement forgets one. Members are singletons with a name and a value, and the value can still be a string for JSON or a database. Plain Enum members never equal raw strings, which keeps statuses from being mixed with other text; StrEnum keeps that equality when compatibility with strings matters more.
-
Mid to senior
Two IntEnums, Priority and Retries, each have a member with the value 3. A bug report says high-priority jobs are sometimes treated as jobs that allow three retries. How can that happen, and what would you change?
-
Mid to senior
Order statuses are an IntEnum with auto() values, stored as integers in a database. A developer adds a new status in the middle of the class. What breaks, how would you detect it before release, and how should statuses be stored?
-
Senior to staff
You are designing user permissions for a document service. Would you use a Flag, a set of Enum members, or a table of grants in the database? Compare them for storage, checking, querying and adding permissions later.
Key takeaways
- An enum is a fixed set of named members, each with a
nameand avalue; members are singletons. Kind(value)looks a member up by value andKind["NAME"]by name; a missing value raisesValueError.- Plain
Enummembers never equal raw values;IntEnumandStrEnummembers are also ints and strings. Flagmembers combine with|and are tested within; match members with dotted names incasepatterns.
Terms in this lesson
- enum member
- One named value of an enumeration, such as OrderStatus.SHIPPED: an object created once with its class, with a name ("SHIPPED") and a value ("shipped"), that cannot be changed and is found again by value or by name.
- enumeration (enum)
- A class whose members form a fixed, named set, such as the statuses of an order. Each member has a name and a value and is created once, when the class is defined; the enum module provides Enum, IntEnum, StrEnum and Flag.
References
- enum: support for enumerations (Python Software Foundation)
- Enum HOWTO (Python Software Foundation)
- enum.Flag (Python Software Foundation)
- enum.StrEnum (Python Software Foundation)
- enum.Enum._missing_() (Python Software Foundation)
- The Python Language Reference: value patterns in match statements (Python Software Foundation)
- http.HTTPStatus (Python Software Foundation)
- json: JSON encoder and decoder (Python Software Foundation)
- PEP 435: adding an enum type to the standard library (Python Software Foundation)
- PEP 663: standardizing enum str(), repr() and format() behaviors (Python Software Foundation)
- re.RegexFlag (Python Software Foundation)
- typing.assert_never(): exhaustiveness checking (Python Software Foundation)
- chmod: change the file modes (POSIX.1-2024) (IEEE and The Open Group)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress