Der Pattern-Matching-Mechanismus wurde in Python 3.10 unter dem Namen Structural Pattern Matching eingeführt und wird durch die Konstruktion match-case implementiert. Dieses Werkzeug ermöglicht es, komplexe Datenstrukturen elegant und prägnant zu analysieren, indem sie mit Mustern verglichen werden.
In vielen funktionalen Programmiersprachen (z.B. in Haskell, Scala) gilt das Pattern Matching seit langem als bequeme Möglichkeit, die Logik basierend auf der Datenstruktur zu verzweigen. Python hatte lange Zeit keinen solchen Mechanismus und kam mit Ketten von if-elif-else oder Entpackung aus.
Komplexe, verschachtelte Datenstrukturen (Dictionaries, Listen, Objekte) erfordern oft zahlreiche Überprüfungen von Typen, Werten und Strukturen, was zu verworrenem Code mit vielen Bedingungen führt.
match-case bietet eine deklarative und lesbare Möglichkeit, verschiedene Szenarien der Datenverarbeitung unter Beachtung von Typ und Struktur zu beschreiben, ohne die Bedingungen zu überladen.
def process(event): match event: case {"type": "click", "x": x, "y": y}: return f"Klick bei ({x}, {y})" case [command, *args]: return f"Befehl: {command}, args: {args}" case _: return "Unbekanntes Ereignis" print(process({"type": "click", "x": 2, "y": 5})) # Klick bei (2, 5) print(process(["RUN", 1, 2, 3])) # Befehl: RUN, args: [1, 2, 3]
Vergleicht match-case nur nach Werten? Nein. Der Vergleich kann auch nach Struktur, Typ, Entpackung von Sammlungen und sogar Eigenschaften von Objekten erfolgen (wenn die speziellen Methoden match_args angegeben werden).
Beeinflusst die Reihenfolge der case in match? Ja. Die Analyse erfolgt von oben nach unten, das erste passende Muster wird ausgelöst. Folgende case werden nicht überprüft.
Kann man benutzerdefinierte Klassen vergleichen? Ja, wenn die Klasse die Methoden match_args und/oder getitem implementiert. Dann kann das Pattern Matching Werte aus den Feldern extrahieren.
class Point: __match_args__ = ("x", "y") def __init__(self, x, y): self.x = x self.y = y def where(obj): match obj: case Point(x, y): return f"Punkt bei {x}, {y}" case _: return "Unbekannt" print(where(Point(1, 2))) # Punkt bei 1, 2
Vorteile:
Negativer Fall: Entwickler verwendeten match-case sogar für einfache boolesche Verzweigungen, was den Code nur komplizierter machte. Vorteile: Sie lernten eine neue Funktion. Nachteile: Verloren an Lesbarkeit und erhöhten die Anzahl der Fehler.
Positiver Fall: In einer Anwendung mit vielen Ereignissen und verschachtelten Strukturen (z.B. einem Graph-Parser) ermöglichte match-case, sperrige if-elif zu vermeiden und das Parsing zu zentralisieren. Vorteile: Die Lesbarkeit stieg, die Anzahl der Bugs sank. Nachteile: Es war Schulung der Teammitglieder zur neuen Konstruktion erforderlich.