W3docs

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 lassen
  • IntEnum — Enums, die sich wie Ganzzahlen verhalten
  • Flag — kombinierbare Bit-Flag-Enums
  • Methoden und Properties zu einem Enum hinzufügen
  • Aliase, @unique und _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 2 fü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) und handle_order(20) sind beide gültiges Python.
  • Refaktorierung ist riskant. Wenn Sie entscheiden, dass 1 etwas anderes bedeuten soll, müssen Sie jede 1 in 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 active

Die 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 = 3

Jedes 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.BLUE

Jedes Member stellt zwei Attribute zur Verfügung:

print(Color.RED.name)      # RED
print(Color.RED.value)     # 1

Verwenden 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 3

Sie können auch die Zugehörigkeit prüfen:

print(Color.RED in Color)   # True

Dies 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 4

auto() 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)  # False

Ein einfaches Enum-Member ist nicht gleich seinem rohen Wert:

print(Color.RED == 1)   # False

Das 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 types

Wenn 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)              # True

Ein 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)     # False

auto() 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 failed

Sie 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.7

Das 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/False mit 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.

Übungen

Übung
What does Color['RED'] do when Color is an Enum with a RED member?
What does Color['RED'] do when Color is an Enum with a RED member?
Was this page helpful?