An order moves through the statuses "pending", "active" and "done".
The strings arrive in JSON and go back out to a database. If you pass plain
strings around, a typo like "actve" passes through your code unnoticed,
and something fails much later, far from the typo. A StrEnum lists the valid values in one place,
and each member still works as a string.
import json
from enum import StrEnum, unique
@unique
class Status(StrEnum):
PENDING = "pending"
ACTIVE = "active"
DONE = "done"
def is_status(status: Status, raw: str) -> bool:
return status == raw
print(Status("active") is Status.ACTIVE) # True
print(is_status(Status.DONE, "done")) # True
print(f"order is {Status.PENDING}") # order is pending
print(json.dumps({"status": Status.ACTIVE})) # {"status": "active"}
print({"done": 2}[Status.DONE]) # 2
Each name in the class body, such as PENDING, is a member, and the
string after = is its value. A member compares equal to its value,
formats as its value, and works as a dictionary key wherever the value
would.
The example puts the comparison in a function on purpose. mypy in strict
mode reports an error for Status.DONE == "done" written out directly. It
treats a member and a string literal as values that can never be equal,
although at runtime the result is True. Comparing a Status parameter
with a str parameter passes mypy.
Parse input at the edge of your program
Status(raw) finds the member whose value is raw. An unknown string
raises ValueError, so a typo fails where the data comes in:
def parse_status(raw: str) -> Status:
return Status(raw)
for raw in ["active", "ACTIVE"]:
try:
print(parse_status(raw).name)
except ValueError as exc:
print("rejected:", exc)
# ACTIVE
# rejected: 'ACTIVE' is not a valid Status
The lookup is exact, so "ACTIVE" and " active" both fail. To look a
member up by its name instead, write Status["ACTIVE"].
The return type Status is narrower than str. When a function takes a
Status parameter, mypy rejects a plain string passed to it. Code after the
parser never sees an unchecked value.
Where it stops acting like a string
A StrEnum member is a real str. Three things still differ from a plain
string:
type(member) is strisFalse. Passstr(member)to code that checks the exact type.- String methods return plain strings.
Status.DONE.upper()is thestr"DONE", not a member. - Members of two different
StrEnumclasses compare equal when their values match, because both equal the same string.
Two older patterns behave differently. A plain Enum member never equals
its value, and json.dumps raises on it. A class Status(str, Enum)
member equals its value, but str() and f-strings give Status.DONE
instead of done. Before you switch an existing enum to StrEnum, check
the places that format, store or compare its members.
Aliases and @unique
When two members have the same value, the second name becomes an alias, another name for the first member:
class Legacy(StrEnum):
OLD = "same"
NEW = "same"
print(Legacy["NEW"] is Legacy.OLD) # True
print(Legacy.NEW.name) # OLD
print(list(Legacy)) # [<Legacy.OLD: 'same'>]
Looping over the class skips aliases. The @unique decorator turns an alias
into an error. It raises ValueError when Python runs the class
definition, which is usually at import time. Keep an alias only on purpose,
for example so that an old name still works. Use @unique when every name
needs its own value. You can also call unique(Legacy) on a class that
already exists. It raises the same error and leaves the class as it was.
Check your understanding
Should parse_status call .strip().lower() before it looks up the member?
Show answer
Hide answer
Only if your program promises to accept those spellings. With that change,
" ACTIVE " becomes a valid status. Without it, the parser rejects it.
Either can be right, so decide on purpose and write it down. The exercise
uses exact values, so normalizing the input would fail its tests.
Going further
- In a
StrEnum,auto()uses the member's name in lower case.PENDING = auto()gives the value"pending". StrEnumarrived in Python 3.11, and the(str, Enum)pattern is what code used before it.- See the enum documentation.