← Home

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, automatic repr, 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
AttributeReturnsExample
.namestrStatus.ACTIVE.name"ACTIVE"
.valueassigned typeStatus.ACTIVE.value"active"
list(MyEnum)list of membersAll enum members
MyEnum["NAME"]memberAccess by name string
MyEnum(value)memberAccess 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"

Resources