OSPython.025: Enums and Named Constants — Safer Choices, States, and Status Codes

Python enums lesson cover with a clear Python terminal, Python logo, and enum status panel on a black background.

Enums make a Python program more explicit by replacing loose strings, magic numbers, and undocumented status values with named members from a controlled set of choices.

OSPython.025 continues the Open Source Python sequence after OSPython.024: Dataclasses Basics. Dataclasses make structured records easier to define. Enums make the allowed states inside those records easier to read, validate, compare, and maintain.

This lesson also builds on OSPython.023: Type Hints and Annotations Basics and OSPython.012: Classes and Objects. An enum is a class-based way to name a closed group of related constants.

The enum idea

identify a small fixed set of allowed values → give each value a meaningful name → use those names instead of raw strings or numbers → compare enum members directly → make invalid states easier to notice → document the domain model in code

Why named constants matter

Programs often contain values that represent states, roles, modes, directions, error codes, or categories. Without enums, those values are often written as plain strings or integers.

status = "forwarding"
role = "switch"
mode = 3

The problem is that raw values do not clearly explain the full allowed set. They also make spelling mistakes, inconsistent capitalization, and invalid values harder to detect.

Harvard CS50P: classes as the foundation

Harvard CS50P — Object-Oriented Programming. Provides the class and object foundation that Python enums build on.

Create an Enum class

Python’s standard library provides the enum module. The basic class is Enum.

from enum import Enum, auto

class PortState(Enum):
    DISABLED = auto()
    BLOCKING = auto()
    FORWARDING = auto()

Each member is accessed from the enum class:

state = PortState.FORWARDING
print(state.name)
print(state.value)

The Python documentation describes Enum as a base class for creating enumerated constants. See Python documentation — enum.

Use enum members instead of loose strings

if state is PortState.FORWARDING:
    print("traffic can pass")

The comparison uses the named enum member. This is clearer than comparing against a string such as "forwarding", because the code points back to the controlled set of allowed states.

Use auto when the number itself is not important

auto() lets Python assign values automatically. This is useful when the identity of the member matters more than the specific stored value.

class JobStatus(Enum):
    QUEUED = auto()
    RUNNING = auto()
    FAILED = auto()
    COMPLETE = auto()

If external systems depend on specific stored values, assign those values explicitly instead of using auto().

mCoding: Enum, IntEnum, and StrEnum

mCoding — Enums in Python are simple but powerful. Covers Enum, IntEnum, StrEnum, and common enum design choices.

Explicit values

Some enums should carry specific values because those values are stored, serialized, logged, or exchanged with other systems.

class DeviceRole(Enum):
    ROUTER = "router"
    SWITCH = "switch"
    FIREWALL = "firewall"

Now the enum member has both a readable member name and a stable value.

role = DeviceRole.SWITCH
print(role.name)   # SWITCH
print(role.value)  # switch

StrEnum for string-like members

Python includes StrEnum for enum members that are also strings. This can be useful for API payloads, configuration records, or logs that need readable string values.

from enum import StrEnum

class DeviceRole(StrEnum):
    ROUTER = "router"
    SWITCH = "switch"
    FIREWALL = "firewall"

StrEnum should still be used carefully. The purpose is not to hide that the value is a string; the purpose is to keep a controlled set of string values attached to a named type.

IntEnum for integer-compatible constants

IntEnum creates enum members that are also integers. It is useful when code must interoperate with an existing integer protocol, file format, status code, or hardware register model.

from enum import IntEnum

class ExitCode(IntEnum):
    OK = 0
    CONFIG_ERROR = 2
    NETWORK_ERROR = 3

Prefer plain Enum when integer compatibility is not required. Integer comparison can make invalid mixing easier if the design is too loose.

Real Python: building enumerations

Real Python — Python’s enum: Start Building Enumerations. Introduces the standard-library enum module and practical enum patterns.

Enums with dataclasses

Enums combine naturally with dataclasses. A dataclass can hold structured fields, while an enum describes which values are legal for a state or category field.

from dataclasses import dataclass
from enum import Enum, StrEnum, auto

class PortState(Enum):
    DISABLED = auto()
    BLOCKING = auto()
    FORWARDING = auto()

class DeviceRole(StrEnum):
    ROUTER = "router"
    SWITCH = "switch"

@dataclass(frozen=True)
class Interface:
    name: str
    state: PortState
    role: DeviceRole

eth0 = Interface(
    name="eth0",
    state=PortState.FORWARDING,
    role=DeviceRole.SWITCH,
)

The result is more expressive than storing "forwarding" and "switch" as unrelated plain strings.

Pattern matching with enums

Enums can make match statements easier to read because each branch names a specific state.

def action_for(state: PortState) -> str:
    match state:
        case PortState.DISABLED:
            return "do not forward"
        case PortState.BLOCKING:
            return "listen only"
        case PortState.FORWARDING:
            return "forward traffic"

This style is clearest when the enum represents a small domain concept with a known set of choices.

Look up members by name or value

Enum members can be looked up by member name or by value.

DeviceRole["SWITCH"]
DeviceRole("switch")

These lookups are useful when parsing external input, but the input should still be validated and errors should be handled explicitly.

Avoid comparing only raw values

The point of an enum is to make the named member meaningful. Comparing only raw values can weaken that benefit.

# Prefer this:
if role is DeviceRole.SWITCH:
    ...

# Usually avoid this inside domain logic:
if role.value == "switch":
    ...

Raw values are most appropriate at system boundaries such as JSON conversion, command-line output, database storage, and network messages.

When not to use an enum

  • the allowed values change frequently at runtime;
  • the values come from a database table that users can modify;
  • the set is large and data-driven rather than code-driven;
  • simple booleans already express the state clearly;
  • the enum would hide rather than clarify the domain.

Enums are strongest when the set of choices is small, meaningful, and stable enough to be part of the program’s model.

Common mistakes

  • using strings everywhere even though the allowed choices are fixed;
  • using IntEnum when plain Enum would be safer;
  • depending on auto() values outside the program;
  • serializing enum values without documenting the format;
  • mixing enum members from different concepts because their raw values happen to match;
  • creating giant enums that should be data instead;
  • checking .value everywhere instead of comparing members.

Practice exercise

Create a network interface model using one dataclass and two enums.

  • Create a PortState enum with DISABLED, BLOCKING, and FORWARDING.
  • Create a DeviceRole StrEnum with ROUTER, SWITCH, and FIREWALL.
  • Create a frozen dataclass named Interface with name, state, and role.
  • Write a function that returns a short action string based on PortState.
  • Parse the string "switch" into a DeviceRole.
  • Explain where enum values should be converted to strings for JSON output.

Knowledge check

What problem does an enum solve?
It names a controlled set of related constant values so code is clearer and invalid choices are easier to identify.

When is auto() appropriate?
When the enum member identity matters but the specific stored value does not need to be stable for external systems.

When should explicit values be assigned?
When values are stored, serialized, logged, exchanged with another system, or required by a protocol.

What is StrEnum useful for?
It is useful for controlled string-valued members, especially at boundaries where readable string values are required.

Why prefer plain Enum over IntEnum by default?
Plain Enum avoids unnecessary integer compatibility and reduces accidental mixing with unrelated integer values.

How do enums help dataclasses?
They make dataclass state fields more explicit by limiting those fields to named domain choices.

Where should raw enum values usually appear?
At boundaries such as JSON, databases, command-line output, configuration files, or network messages.

Key takeaway

Enums turn informal choices into named program structure. They reduce magic strings and unexplained numbers, work naturally with dataclasses and type hints, and make states, roles, modes, and status codes easier to read and maintain.

Engineering note: Enum design should preserve stable external formats only when the values are part of a documented interface. Internal enum values may be implementation details, but serialized enum values become compatibility commitments.

Display note: this lesson uses standard Gutenberg paragraphs, headings, lists, code, and media embeds only. No decorative text-box or callout-box layout is used.

BitcoinVersus.Tech

Advertisement

BitcoinVersus.Tech advertisement.

Editor’s Note:

We volunteer daily to ensure the credibility of the information on this platform is Verifiably True. If you would like to support our research initiatives, please donate here: 3C9o19EH5HSiwEPyCTmEKzxhNCbo2X6TTb

BitcoinVersus.tech is not a financial advisor. This media platform reports on financial subjects purely for informational purposes.

Leave a comment