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
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
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
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
IntEnumwhen plainEnumwould 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
.valueeverywhere instead of comparing members.
Practice exercise
Create a network interface model using one dataclass and two enums.
- Create a
PortStateenum withDISABLED,BLOCKING, andFORWARDING. - Create a
DeviceRoleStrEnumwithROUTER,SWITCH, andFIREWALL. - Create a frozen dataclass named
Interfacewithname,state, androle. - Write a function that returns a short action string based on
PortState. - Parse the string
"switch"into aDeviceRole. - 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
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