Skip to Content
Course content

239: The enum Module in Depth

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're designing a system for a coffee shop. You have specific drink sizes: Small, Medium, and Large. Now, you could represent these as strings ("small", "medium", "large") or as integers (1, 2, 3). But here is the problem: strings are prone to typos—one developer writes "Small" and another writes "small", and suddenly your order logic is broken. Integers are worse; six months from now, you'll look at if size == 2: and have no idea what "2" actually means without hunting down a comment in another file.

An Enum (short for Enumeration) is like a physical, printed menu. It defines a fixed set of named constants that cannot be changed. Instead of guessing what a value means, you refer to a specific "menu item."

Replacing Magic Strings with Symbolic Names

In Python, we use the enum module to create these sets. I usually recommend Enum as your starting point. It creates a class where the members are constants. Let's map our coffee sizes to this concept:

from enum import Enum

class CoffeeSize(Enum):
    SMALL = 1
    MEDIUM = 2
    LARGE = 3

# Now we use it like this
my_order = CoffeeSize.MEDIUM

if my_order == CoffeeSize.MEDIUM:
    print("Coming right up!")

Notice that CoffeeSize.MEDIUM is not just the number 2; it's a member of the CoffeeSize class. It has a name ("MEDIUM") and a value (2). This separation is what saves your skin during debugging.

Letting Python Handle the Numbering

I'll be honest: most of the time, I don't actually care if "Small" is 1 or 100. I just care that it's unique. Manually assigning integers is tedious and error-prone. That's where auto() comes in. It tells Python, "Just give this a unique value; I don't care what it is."

from enum import Enum, auto

class OrderStatus(Enum):
    PENDING = auto()
    PREPARING = auto()
    READY = auto()
    DELIVERED = auto()

This keeps your code clean and prevents the "off-by-one" errors that happen when you're manually numbering a list of twenty different states.

The Danger of Duplicate Values

By default, Enum allows different names to have the same value. This is called "aliasing." While sometimes useful, it's usually a mistake. If you accidentally assign SMALL = 1 and TINY = 1, Python won't complain, but your logic might get weird.

To stop this, use the @unique decorator. It forces Python to raise a ValueError if you accidentally reuse a value.

from enum import Enum, auto, unique

@unique
class CoffeeSize(Enum):
    SMALL = 1
    MEDIUM = 2
    LARGE = 1  # This will now trigger a ValueError immediately!

When You Actually Need Integers: IntEnum

Standard Enum members aren't integers; they are objects. If you try to compare CoffeeSize.SMALL == 1, it will return False. But what if you're interfacing with a legacy database or a JSON API that expects actual numbers?

That's where IntEnum comes in. It behaves exactly like an Enum, but it also inherits from int, making it compatible with integer comparisons.

from enum import IntEnum

class Priority(IntEnum):
    LOW = 1
    HIGH = 2

# This works with IntEnum, but would fail with a regular Enum
if Priority.LOW == 1:
    print("This is low priority.")

Iterating and Inspecting Your Enums

One of the best things about enums is that they are iterable. I often use this to populate dropdown menus in UI code or to validate input from a user.

for size in CoffeeSize:
    print(f"Option: {size.name} (Value: {size.value})")

You can also look up a member by its value using CoffeeSize(1), which returns CoffeeSize.SMALL. This is incredibly handy when converting raw data from a database back into a typed Python object.




📋 Practical Task

Build a Document Approval Workflow State Machine

You are building a system for a legal firm to track the status of contracts. You need to implement a state machine that ensures documents move through a specific lifecycle.

Requirements:

  • Create an Enum called DocumentState using the @unique decorator.
  • The states should be: DRAFT, UNDER_REVIEW, APPROVED, and REJECTED. Use auto() for the values.
  • Write a function called transition_document(current_state, action).
  • The function should take the current DocumentState and a string action (e.g., "submit", "approve", "reject").
  • Logic:
    • If state is DRAFT and action is "submit" $\rightarrow$ return UNDER_REVIEW.
    • If state is UNDER_REVIEW and action is "approve" $\rightarrow$ return APPROVED.
    • If state is UNDER_REVIEW and action is "reject" $\rightarrow$ return REJECTED.
    • For any other invalid combination, return the current_state unchanged.

Test your code: Start a document in DRAFT, transition it to UNDER_REVIEW, and then transition it to APPROVED. Print the .name of the state at each step.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.