Python Enums
Python Enums erklärt: Enum, IntEnum, Flag und auto() erstellen, Methoden hinzufügen, sicher vergleichen und Magic Numbers im Code ersetzen.
Ein Enum (kurz für Enumeration) ist eine Menge benannter, konstanter Werte, die unter einem einzigen Typ zusammengefasst sind. Anstatt rohe Ganzzahlen oder Zeichenketten wie 1, 2, "pending", "active" überall im Code zu verstreuen, geben Sie jedem einen beschreibenden Namen — Status.PENDING, Color.RED — und Python garantiert, dass dieser Name immer auf denselben Wert verweist.
Dieses Kapitel behandelt:
- Warum Enums existieren und welche Probleme sie lösen
- Erstellen eines Enums mit der
Enum-Klasse - Zugriff auf Member nach Name und Wert
- Iteration über ein Enum
auto()— Python Werte automatisch zuweisen lassenIntEnum— Enums, die sich wie Ganzzahlen verhaltenFlag— kombinierbare Bit-Flag-Enums- Methoden und Properties zu einem Enum hinzufügen
- Aliase,
@uniqueund_missing_ - Wann man Enums gegenüber anderen Mustern bevorzugt
Bevor Sie dieses Kapitel lesen, sollten Sie mit Python-Klassen und -Objekten und Python-Datentypen vertraut sein.
Warum Enums verwenden?
Betrachten Sie diese Funktion, die einen als einfache Ganzzahl übergebenen Bestellstatus verarbeitet:
def handle_order(status):
if status == 1:
print("Order is pending")
elif status == 2:
print("Order is active")
elif status == 3:
print("Order is complete")Das funktioniert, hat aber echte Probleme:
- Magic Numbers. Was bedeutet
2für sich allein? Sie müssen bis zur Funktionsdefinition zurückverfolgen. - Keine Validierung.
handle_order(99)tut stillschweigend nichts — kein Fehler, keine Warnung. - Tippfehler sind unsichtbar.
handle_order(2)undhandle_order(20)sind beide gültiges Python. - Refaktorierung ist riskant. Wenn Sie entscheiden, dass
1etwas anderes bedeuten soll, müssen Sie jede1in der Codebasis finden.
Enums beheben all diese Probleme. Die gleiche Logik mit einem Enum geschrieben ist selbstdokumentierend, sicher und refaktorierungsfreundlich:
from enum import Enum
class OrderStatus(Enum):
PENDING = 1
ACTIVE = 2
COMPLETE = 3
def handle_order(status: OrderStatus):
if status == OrderStatus.PENDING:
print("Order is pending")
elif status == OrderStatus.ACTIVE:
print("Order is active")
elif status == OrderStatus.COMPLETE:
print("Order is complete")
handle_order(OrderStatus.ACTIVE) # Order is activeDie Absicht ist klar, und Python verhindert, dass handle_order(99) versehentlich zu einem Zweig passt.
Ein Enum erstellen
Importieren Sie Enum aus dem enum-Modul (Teil der Python-Standardbibliothek — keine Installation erforderlich) und erstellen Sie eine Unterklasse:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3Jedes Klassenattribut (RED, GREEN, BLUE) wird zu einem Enum-Member. Die Werte auf der rechten Seite (1, 2, 3) können Ganzzahlen, Zeichenketten oder ein beliebiger anderer Typ sein — die Wahl liegt bei Ihnen.
Auf Member zugreifen
Es gibt drei Möglichkeiten, auf ein Enum-Member zuzugreifen:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
# Attribute access (most common)
print(Color.RED) # Color.RED
# By name (square bracket notation)
print(Color['GREEN']) # Color.GREEN
# By value (call the class with the value)
print(Color(3)) # Color.BLUEJedes Member stellt zwei Attribute zur Verfügung:
print(Color.RED.name) # RED
print(Color.RED.value) # 1Verwenden Sie .name, wenn Sie eine für Menschen lesbare Bezeichnung benötigen (für Logging oder Anzeige), und .value, wenn Sie den zugrunde liegenden Wert an ein externes System (eine Datenbank, eine API) übergeben müssen.
repr und type
print(repr(Color.RED)) # <Color.RED: 1>
print(type(Color.RED)) # <enum 'Color'>Ein Enum-Member ist eine Instanz seiner Enum-Klasse, nicht von int oder str.
Über ein Enum iterieren
Enums sind iterierbar. Die Iteration liefert Member in der Definitionsreihenfolge:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
for color in Color:
print(color.name, color.value)
# RED 1
# GREEN 2
# BLUE 3Sie können auch die Zugehörigkeit prüfen:
print(Color.RED in Color) # TrueDies macht Enums praktisch für die Befüllung von Dropdown-Menüs, den Aufbau von switch-ähnlichen Dispatch-Tabellen oder die Erstellung von Auswahllisten für Benutzereingaben.
auto() — Automatische Werte
Wenn die konkreten Werte keine Rolle spielen — Sie nur möchten, dass jedes Member eindeutig ist — verwenden Sie auto(). Python weist aufeinanderfolgende Ganzzahlen ab 1 zu:
from enum import Enum, auto
class Direction(Enum):
NORTH = auto()
SOUTH = auto()
EAST = auto()
WEST = auto()
for d in Direction:
print(d.name, d.value)
# NORTH 1
# SOUTH 2
# EAST 3
# WEST 4auto() ist besonders nützlich, wenn das Enum im Laufe der Zeit wachsen wird und Sie Member nicht manuell umnummerieren möchten.
Enum-Member vergleichen
Verwenden Sie is oder == zum Vergleich von Membern. Beide funktionieren, aber is ist etwas schneller, da Enum-Member Singletons sind — jeder Name verweist auf genau ein Objekt:
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
print(Color.RED is Color.RED) # True
print(Color.RED == Color.RED) # True
print(Color.RED == Color.GREEN) # FalseEin einfaches Enum-Member ist nicht gleich seinem rohen Wert:
print(Color.RED == 1) # FalseDas ist beabsichtigt. Es verhindert versehentliche Gleichheit zwischen verschiedenen Enums, die dieselbe Ganzzahl teilen:
class Size(Enum):
SMALL = 1
print(Color.RED == Size.SMALL) # False — different typesWenn Sie wertbasierte Vergleiche benötigen (z. B. member > 1), verwenden Sie stattdessen IntEnum (siehe unten).
IntEnum — Enums, die sich wie Ganzzahlen verhalten
IntEnum-Member sind auch reguläre Python-Ganzzahlen. Das bedeutet, Sie können Arithmetik, Vergleichsoperatoren verwenden und sie überall dort einsetzen, wo ein int erwartet wird:
from enum import IntEnum
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
print(Priority.HIGH > Priority.LOW) # True
print(Priority.MEDIUM + 10) # 12
print(Priority.HIGH == 3) # TrueEin häufiger Anwendungsfall ist das Sortieren einer Liste von Enum-Membern:
from enum import IntEnum
class Level(IntEnum):
LOW = 1
MED = 2
HIGH = 3
levels = [Level.HIGH, Level.LOW, Level.MED]
print([l.name for l in sorted(levels)]) # ['LOW', 'MED', 'HIGH']Wann Enum gegenüber IntEnum bevorzugen
IntEnums Integer-Transparenz ist auch seine Schwäche: Priority.HIGH == 3 ist True, sodass ein falsch eingetipptes Literal 3 stillschweigend gleich Priority.HIGH verglichen wird. Verwenden Sie einfaches Enum, wenn Sie strikte Typsicherheit möchten, und IntEnum nur, wenn Sie wirklich ganzzahlige Arithmetik benötigen oder mit einer API interagieren müssen, die mit rohen Zahlen arbeitet.
Flag — Kombinierbare Bit-Flag-Enums
Flag ist für Szenarien konzipiert, in denen mehrere Optionen gleichzeitig aktiv sein können. Seine Member sind Zweierpotenzen, und Sie kombinieren sie mit dem |-Operator (bitweises ODER):
from enum import Flag, auto
class Permission(Flag):
READ = auto()
WRITE = auto()
EXECUTE = auto()
ALL = READ | WRITE | EXECUTE
user = Permission.READ | Permission.WRITE
print(user) # Permission.WRITE|READ
print(Permission.READ in user) # True
print(Permission.EXECUTE in user) # Falseauto() innerhalb von Flag weist aufeinanderfolgende Zweierpotenzen zu (1, 2, 4, 8, …), sodass die Kombination von Membern mit | nie zu mehrdeutigen Ergebnissen führt.
Verwenden Sie Flag für Berechtigungssysteme, Feature-Toggles und jede Situation, in der Sie einen kompakten Satz boolescher Schalter benötigen.
Methoden und Properties hinzufügen
Da ein Enum eine Klasse ist, können Sie ihm Methoden und Properties hinzufügen. Das hält verwandte Logik innerhalb des Typs, anstatt sie über if/elif-Ketten zu verteilen:
from enum import Enum
class HttpStatus(Enum):
OK = 200
CREATED = 201
NOT_FOUND = 404
INTERNAL_ERROR = 500
@property
def is_success(self):
return 200 <= self.value < 300
@property
def is_error(self):
return self.value >= 400
def handle_response(status: HttpStatus):
if status.is_success:
print(f"{status.value} {status.name}: request succeeded")
elif status.is_error:
print(f"{status.value} {status.name}: request failed")
handle_response(HttpStatus.OK) # 200 OK: request succeeded
handle_response(HttpStatus.NOT_FOUND) # 404 NOT_FOUND: request failedSie können einem Enum auch ein benutzerdefiniertes __init__ geben, um zusätzliche Daten pro Member zu speichern. Geben Sie die Werte als Tupel an:
from enum import Enum
class Planet(Enum):
MERCURY = (3.303e+23, 2.4397e6)
VENUS = (4.869e+24, 6.0518e6)
EARTH = (5.976e+24, 6.37814e6)
def __init__(self, mass, radius):
self.mass = mass
self.radius = radius
@property
def surface_gravity(self):
G = 6.67430e-11
return G * self.mass / (self.radius ** 2)
print(round(Planet.EARTH.surface_gravity, 2)) # 9.8
print(round(Planet.MERCURY.surface_gravity, 2)) # 3.7Das Tupel (mass, radius) wird zu den Konstruktorargumenten; self.value enthält weiterhin das vollständige Tupel.
Aliase und @unique
Wenn zwei Member denselben Wert teilen, wird das zweite zu einem Alias — es wird zum ersten Member aufgelöst. Aliase werden bei der Iteration nicht zurückgegeben:
from enum import Enum
class Status(Enum):
ACTIVE = 1
RUNNING = 1 # alias for ACTIVE
print(Status.ACTIVE is Status.RUNNING) # True
print(list(Status)) # [<Status.ACTIVE: 1>]Aliase sind gelegentlich nützlich (z. B. ein Legacy-Name, der auf einen neuen verweist), können aber auch Tippfehler verdecken. Verwenden Sie den @unique-Decorator, um doppelte Werte vollständig zu verbieten:
from enum import Enum, unique
@unique
class Status(Enum):
PENDING = 1
ACTIVE = 2
INACTIVE = 3
# Trying to add a duplicate value to a @unique enum raises ValueError:
# ValueError: duplicate values found in <enum 'Bad'>: B -> A@unique ist ein guter Standard für jedes Enum, bei dem versehentliche Aliasierung ein Fehler wäre.
Benutzerdefinierte Suche mit _missing_
Standardmäßig löst Color('unknown') einen ValueError aus. Sie können die Klassenmethode _missing_ überschreiben, um nicht erkannte Werte zu behandeln — zum Beispiel für eine Suche ohne Berücksichtigung der Groß-/Kleinschreibung:
from enum import Enum
class Color(Enum):
RED = 'red'
GREEN = 'green'
BLUE = 'blue'
@classmethod
def _missing_(cls, value):
if isinstance(value, str):
for member in cls:
if member.value == value.lower():
return member
return None
print(Color('RED')) # Color.RED
print(Color('Green')) # Color.GREEN_missing_ empfängt den nicht gefundenen Wert. Geben Sie das passende Member zurück oder None (was Python seinen Standard-ValueError auslösen lässt).
Wann Enums verwenden
Enums sind die richtige Wahl, wenn:
- Eine Variable nur einen aus einer festen Menge benannter Zustände halten kann (Bestellstatus, HTTP-Verb, Kartenfarbe).
- Sie ungültige Werte daran hindern möchten, stillschweigend durchzukommen.
- Dasselbe Konzept an mehreren Stellen verglichen wird und Sie eine einzige Wahrheitsquelle möchten.
- Sie über alle gültigen Werte iterieren müssen (Formular befüllen, API dokumentieren).
Sie benötigen wahrscheinlich kein Enum, wenn:
- Die Menge der Werte offen ist oder sich zur Laufzeit ändert (verwenden Sie ein Dictionary oder eine Datenbank-Lookup-Tabelle).
- Sie nur zwei Zustände benötigen —
True/Falsemit einer klaren booleschen Bedeutung ist einfacher. - Die Werte aus Benutzereingaben stammen, die gegen ein Schema validiert werden müssen — erwägen Sie eine Bibliothek wie Pydantic, die sich nahtlos mit Python-Enums integriert.
Für eng verwandte Muster, siehe Python Dataclasses (für strukturierte Daten mit Standardwerten) und Python abstrakte Klassen (für die Durchsetzung von Schnittstellenverträgen über Unterklassen hinweg). Wenn Sie benannte Konstantencontainer ohne den vollen Enum-Mechanismus benötigen, bietet das Python Collections-Modul namedtuple als Alternative.