109. Enum Module
Enumerated constants with enum.Enum
109. Enum Module
Rohan has alert levels scattered through his code as strings: "warning", "critical", "ok". A typo — "critcal" — silently never matches anything. The alert system appears to work but misses half its alerts.
Before:
def classify(percent):
if percent < 80:
return "ok"
elif percent < 90:
return "warning"
else:
return "critcal" # typo — silently wrong
if level == "critical": # never matches "critcal"
send_alert()After, with Enum:
from enum import Enum
class AlertLevel(Enum):
OK = "ok"
WARNING = "warning"
CRITICAL = "critical"
def classify(percent) -> AlertLevel:
if percent < 80:
return AlertLevel.OK
elif percent < 90:
return AlertLevel.WARNING
else:
return AlertLevel.CRITICAL
if level == AlertLevel.CRITCAL: # AttributeError immediately — typo caught at definition
send_alert()Typos in string values go undetected at runtime. Typos in Enum member names raise AttributeError the moment Python loads the code — before any user sees the bug.
Enum vs constants:
CRITICAL = "critical"at module level also works. Enum adds: iteration over all members, automaticrepr, grouping related constants into a namespace, and type safety for function signatures (def f(level: AlertLevel)).
💡 Fun fact: The enum module was added to Python 3.4 (2013) via PEP 435, inspired by Java’s enum type. Python also provides IntEnum (where members are actual integers and can be compared with <, >), Flag and IntFlag (for bitwise operations), and StrEnum (Python 3.11+, where members are actual strings). The auto() function lets Python auto-assign values: LOW = auto() assigns the next available integer.
⚠️ Watch out: An Enum member is never equal to its raw value. Status.ACTIVE == "active" is always False, even though Status.ACTIVE.value == "active" is True. This is intentional — Enum members have their own type. Use Status("active") to look up a member by value, or Status["ACTIVE"] to look up by name.
🤔 Think about it: Enum iteration gives all members in definition order: list(Status) returns [Status.PENDING, Status.ACTIVE, Status.DONE]. Could you use this to build a UI dropdown without maintaining a separate list? What happens if you add a new member to the Enum — does the rest of your code automatically pick it up?
Learning objectives
- Define Enum classes with string and integer values
- Compare enum members with == against other enum members (not strings)
- Access .value and .name attributes on enum members
- Iterate over all members of an Enum
Key concepts
- class MyEnum(Enum) — define enumerated constants
- .value — the assigned value (str, int, etc.)
- .name — the identifier as a string
- Enum vs string comparison — always use Enum.MEMBER
- Iterating over Enum members
Try it
Concept detail
enum.Enum — Named Constants Without Magic Strings
Enums replace error-prone string or integer constants with named, type-safe members. A typo like Status.ACTVE raises AttributeError immediately — unlike "actve" which silently never matches and produces bugs that are invisible at runtime.
Defining an Enum
from enum import Enum
class Status(Enum):
PENDING = "pending"
ACTIVE = "active"
DONE = "done"Accessing Members
s = Status.ACTIVE
s.name # "ACTIVE" ← the identifier (always a string)
s.value # "active" ← what you assigned
str(s) # "Status.ACTIVE"Comparing Enum Members
Always compare to the Enum member, never to a raw string or integer:
# WRONG — always False (Enum member is not equal to a plain string):
if status == "active": ...
# CORRECT — type-safe comparison:
if status == Status.ACTIVE: ...Iterating Over All Members
for s in Status:
print(s.name, s.value)
# PENDING pending
# ACTIVE active
# DONE done| Attribute | Returns | Example |
|---|---|---|
.name | str | Status.ACTIVE.name → "ACTIVE" |
.value | assigned type | Status.ACTIVE.value → "active" |
list(MyEnum) | list of members | All enum members |
MyEnum["NAME"] | member | Access by name string |
MyEnum(value) | member | Access by value |
Solution
from enum import Enum
class Priority(Enum):
LOW = 1
MEDIUM = 2
HIGH = 3
CRITICAL = 4
class Status(Enum):
PENDING = "pending"
ACTIVE = "active"
DONE = "done"
def is_active(status: Status) -> bool:
"""Return True if the status is ACTIVE."""
return status == Status.ACTIVE # compare to Enum member, not string
def priority_label(priority: Priority) -> str:
"""Return a human-readable label for the priority."""
return priority.value # .value returns the assigned value (int or str)
def get_high_priority_tasks(tasks: list) -> list:
"""Return tasks with HIGH or CRITICAL priority."""
return [t for t in tasks if t["priority"] in (Priority.HIGH, Priority.CRITICAL)]
def all_statuses() -> list:
"""Return a list of all Status values as strings."""
return [s.value for s in Status]Tests
def test_is_active_returns_true_for_active():
result = is_active(Status.ACTIVE)
assert result is True, f"is_active(Status.ACTIVE) should be True, got {result}"
def test_is_active_returns_false_for_pending():
result = is_active(Status.PENDING)
assert result is False, f"is_active(Status.PENDING) should be False, got {result}"
def test_is_active_string_comparison_fails():
# Enum vs string is always False — even for "active"
result = is_active(Status.ACTIVE)
# If the bug is present, Status.ACTIVE == "active" → False, so result would be False
assert result is True, "Enum members must be compared with Enum.MEMBER, not strings"
def test_priority_label_returns_value():
result = priority_label(Priority.HIGH)
assert result == 3, f"Priority.HIGH.value is 3, got {result}"
def test_priority_label_low():
result = priority_label(Priority.LOW)
assert result == 1, f"Priority.LOW.value is 1, got {result}"
def test_get_high_priority_tasks_filters_correctly():
tasks = [
{"name": "Write tests", "priority": Priority.HIGH},
{"name": "Fix typo", "priority": Priority.LOW},
{"name": "Deploy app", "priority": Priority.CRITICAL},
{"name": "Update docs", "priority": Priority.MEDIUM},
]
result = get_high_priority_tasks(tasks)
names = [t["name"] for t in result]
assert "Write tests" in names
assert "Deploy app" in names
assert "Fix typo" not in names
assert "Update docs" not in names
def test_all_statuses_returns_all_values():
result = all_statuses()
assert set(result) == {"pending", "active", "done"}, f"Got: {result}"
def test_enum_name_attribute():
# .name gives the member name as a string
assert Status.ACTIVE.name == "ACTIVE"
assert Priority.CRITICAL.name == "CRITICAL"