Python hatte lange kein klassisches Switch-Case-Konstrukt. Seit Python 3.10 bekommst du mit strukturellem Musterabgleich (match-case) eine deutlich stärkere Lösung, die weit über das hinausgeht, was du aus Sprachen wie C, C++ oder Java kennst. In diesem Artikel zeige ich dir, wie du match-case effektiv einsetzt, wo es dem klassischen „python switch case“-Gedanken überlegen ist, welche Muster es gibt, worauf du achten musst und wie du von Best Practices profitierst.
Warum es in Python nie ein klassisches Switch-Case gab
Bevor match-case kam, gab es in Python bewusst keinen Switch-Case. Stattdessen hast du typischerweise Folgendes verwendet:
- if-elif-else für einfache, lineare Verzweigungen
- Dictionary-Dispatch (Sprungtabellen) für Zuordnung von Werten zu Funktionen
# if-elif-else
def classify_http(status):
if status == 200:
return "OK"
elif status == 404:
return "Not Found"
elif status == 500:
return "Server Error"
else:
return "Unknown"
# Dictionary-Dispatch
def ok(): return "OK"
def not_found(): return "Not Found"
def server_error(): return "Server Error"
def unknown(): return "Unknown"
dispatch = {
200: ok,
404: not_found,
500: server_error,
}
result = dispatch.get(status, unknown)()
Essenz: Beide Ansätze funktionieren – aber sie werden schnell unübersichtlich, sobald komplexe Datenstrukturen, Verschachtelungen oder zusätzliche Bedingungen ins Spiel kommen. Genau hier setzt match-case an.
Strukturelles Musterabgleichen: Was match-case wirklich ist
Mit Python 3.10 wurde strukturelles Musterabgleichen eingeführt. Es ist ein deklarativer Ansatz: Du beschreibst, welches Form (Pattern) ein Wert haben muss, statt in Schritten zu prüfen, welche Bedingungen gelten. Das macht komplexe Fallunterscheidungen übersichtlicher.
Ein einfaches Beispiel:
day = "Monday"
match day:
case "Saturday" | "Sunday":
print(f"{day} is a weekend.")
case "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday":
print(f"{day} is a weekday.")
case _:
print("That's not a valid day of the week.")
- Kein Fallthrough: Der erste passende
casegewinnt. Es braucht keinbreak. - OR-Muster: Mit
|kombinierst du Alternativen in einer Zeile. - Default-Fall:
_ist der Wildcard-Case (entspricht „default“).
Wichtig: match-case ist ein Statement, kein Ausdruck. Anders als Rusts match liefert es keinen Wert zurück – du führst Code-Blöcke aus.

Die Musterfamilie im Überblick
match-case unterstützt eine Vielzahl von Pattern-Typen, mit denen du nicht nur skalare Werte, sondern auch Sequenzen, Mappings und Klasseninstanzen „dekonstruieren“ kannst.
| Muster | Beispiel | Zweck |
|---|---|---|
| Literal | case 0:, case "POST": |
Exakter Vergleich auf konkrete Werte |
| Capture | case x: |
Bindet den Wert an den Variablennamen x |
| Wildcard | case _: |
Passt auf alles, ohne zu binden (Default) |
| OR-Muster | case "a" | "b": |
Alternativen in einem Case, gleiche Bindungen erforderlich |
| Sequenz | case [x, y]:, case [x, *rest]: |
Matcht Listen/Tupel u.ä., dekonstruieren mit Sternchen |
| Mapping | case {"id": id, **rest}: |
Matcht Dictionaries, extrahiert Schlüssel/Werte |
| Klasse | case Point(x, y):, case Color.RED: |
Matcht Instanzen inkl. Attributbindung, Wertmuster mit Punkt |
| Guard | case [x, y] if x < y: |
Zusatzbedingung nach Musterbindung |
| As-Bindung | case pattern as name: |
Binde das gesamte Subject zusätzlich an name |
Literal-, Capture- und Wildcard-Muster
Die Grundlagen:
def normalize(val):
match val:
case None:
return "missing"
case True | False:
return "bool"
case 0:
return "zero"
case x: # Capture: bindet alles, was vorher nicht matched
return f"value={x}"
Wichtig zu wissen:
- Einzelne Namen sind Capture-Pattern, keine Konstanten. Wenn du konstante Namen (z. B.
RED) matchen willst, nutze punktierte Namen wieColor.REDoder kapsle sie in einem Namespace. _ist eine Konvention für „Ignorieren“. Es bindet nicht.
Sequenzmuster: Listen und Tupel dekonstruieren
Mit Sequenzmustern kannst du Positionen binden und Restlisten erfassen:
def handle_command(tokens):
match tokens:
case ["go", direction]:
return f"Move to {direction}"
case ["drop", *objects]:
return f"Dropping {len(objects)} objects"
case ["use", obj, "on", target]:
return f"Use {obj} on {target}"
case []:
return "No command"
case _:
return "Unknown command"
- Starred patterns wie
*objectsfangen 0..n Elemente ab. - Spezialfall Strings: Obwohl Strings sequenziell sind, werden sie standardmäßig nicht als Sequenzen im Muster behandelt, um unerwünschte Zerlegung zu vermeiden.
Mappingmuster: Dictionaries und JSON zerlegen
Ideal für API-Responses oder Konfigs:
def process_event(evt):
match evt:
case {"type": "user", "id": user_id, **rest}:
return f"User event for {user_id}, extra={list(rest.keys())}"
case {"type": "order", "total": total} if total > 100:
return "High value order"
case {"type": "order"}:
return "Regular order"
case _:
return "Unknown event"
**restsammelt nicht explizit genannte Schlüssel.- Schlüssel in Mappingmustern sind i. d. R. Literale oder wertbasierte Muster (z. B. Enum-Werte).
Klassenmuster: Instanzen elegant matchen
Du kannst eigene Klasseninstanzen matchen, inklusive Attributdekonstruktion. Für Positionsargumente steuert __match_args__ die Reihenfolge.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
__match_args__ = ("x", "y") # Reihenfolge für Positionen
def locate(p):
match p:
case Point(0, 0):
return "Origin"
case Point(x, 0):
return f"X-Axis at {x}"
case Point(0, y):
return f"Y-Axis at {y}"
case Point(x, y) if x == y:
return f"On diagonal at {x}"
case Point(x=x, y=y): # Schlüsselbasierter Match
return f"Point({x}, {y})"
Auch Enums sind super für wertbasierte Muster:
from enum import Enum
class Status(Enum):
OK = 200
NOT_FOUND = 404
ERROR = 500
def categorize(code):
match code:
case Status.OK.value:
return "Everything fine"
case Status.NOT_FOUND.value:
return "Missing"
case Status.ERROR.value:
return "Server down"
case _:
return "Unknown"
Guards und OR-Muster in Aktion
Guards sind if-Bedingungen nach einem Match. Sie werden nachdem alle Variablen gebunden wurden, geprüft:
def route(tokens, exits):
match tokens:
case ["go", direction] if direction in exits:
return f"Going {direction}"
case ["go", _]:
return "You can't go that way"
case _:
return "Unknown command"
OR-Muster sparen Boilerplate:
def is_vowel(ch):
match ch:
case "a" | "e" | "i" | "o" | "u":
return True
case _:
return False
Wichtig: Alle Alternativen in einem OR-Muster müssen die gleichen Namen binden (gleiches Bindungsverhalten).
As-Bindung und verschachtelte Muster
Du kannst ein Teilmuster matchen und zugleich das Ganze binden:
def summarize(data):
match data:
case ["items", *values] as full if all(v >= 0 for v in values):
return f"{full} are all non-negative"
case _:
return "Unsupported"
Praxis: Typische Anwendungsfälle
1) HTTP-Methoden und Statuscodes routen
def handle_http(method, status, payload=None):
match (method, status, payload):
case ("GET", 200, {"items": items}):
return f"Fetched {len(items)} items"
case ("POST", 201, {"id": new_id}):
return f"Created resource {new_id}"
case ("DELETE", 204, _):
return "Deleted"
case ("GET" | "POST" | "PUT" | "DELETE", code, _) if 400 <= code < 500:
return "Client error"
case (_, code, _) if 500 <= code < 600:
return "Server error"
case _:
return "Unhandled response"
2) JSON/ETL: Strukturiert transformieren
def transform(record):
match record:
case {"type": "user", "profile": {"name": name, "age": age, **_}}:
return {"entity": "user", "name": name, "age": age}
case {"type": "order", "lines": [first, *rest]}:
return {"entity": "order", "lines": 1 + len(rest)}
case {"type": "metric", "value": v} if v is not None:
return {"entity": "metric", "value": float(v)}
case _:
return {"entity": "unknown"}
3) CLI-Parser: Tokens interpretieren
def run_cli(args):
match args:
case ["init"]:
return "Initialize repo"
case ["commit", "-m", msg]:
return f"Commit with message: {msg}"
case ["push", remote, branch]:
return f"Push to {remote}/{branch}"
case ["help"] | []:
return "Show help"
case _:
return "Unknown command"
4) Data-Science: Kategorien bilden
def animal_class(animal):
match animal:
case "Eagle" | "Parrot":
return "Bird"
case "Lion" | "Tiger":
return "Mammal"
case "Python" | "Crocodile":
return "Reptile"
case _:
return "Unknown"
5) AST/Interpreter: Ausdrucksbäume
@dataclass
class Add: left: "Expr"; right: "Expr"
@dataclass
class Mul: left: "Expr"; right: "Expr"
@dataclass
class Num: value: int
Expr = Add | Mul | Num
def eval_expr(expr):
match expr:
case Num(value=v):
return v
case Add(left=a, right=b):
return eval_expr(a) + eval_expr(b)
case Mul(left=a, right=b):
return eval_expr(a) * eval_expr(b)
case _:
raise ValueError("Unknown expression")
Vergleich: if-elif, Dictionary-Dispatch, match-case
| Kriterium | if-elif-else | Dict-Dispatch | match-case |
|---|---|---|---|
| Lesbarkeit bei vielen Fällen | Sinkt | Gut bei flachen Zuordnungen | Sehr gut, klar strukturiert |
| Komplexe Datenstrukturen | Umständlich | Kaum geeignet | Stark (Sequenzen, Mappings, Klassen) |
| Erweiterbarkeit | Mittel | Gut (Schlüssel ergänzen) | Sehr gut (neue Muster) |
| Exhaustivitäts-Prüfung | Manuell | Nicht anwendbar | Mit Typchecker/Enum gut möglich |
| Performance | Gut | Sehr gut für O(1)-Lookup | Gut; Fokus auf Lesbarkeit |
| Fallthrough vermeiden | N/A | N/A | Automatisch (kein break nötig) |
Faustregel:
- Nimm if-elif, wenn es nur wenige, einfache Bedingungen sind.
- Nimm Dict-Dispatch, wenn du Werte auf Funktionen abbildest.
- Nimm match-case, wenn Struktur und Muster zählen (verschachtelte Daten, mehrere Formen, Guards).

Best Practices für match-case
- Konkretes vor Allgemeinem: Ordne
case-Blöcke vom spezifischen zum generischen.case _immer nach unten. - Enums und Literals nutzen: Für Zustand/Varianten Enums einsetzen. Das erleichtert Verständnis und Typsicherheit.
- Guards gezielt verwenden: Bedingte Logik in Guards halten, nicht im Muster überkomplizieren.
- As-Bindings für Re-Use: Wenn du das ganze Subjekt im Case brauchst, nutze
as. - Keine überkomplexen Muster: Wenn ein Case unleserlich wird, extrahiere die Logik in Hilfsfunktionen.
- Namenskonflikte vermeiden: Vermeide Single-Namen, wenn du Konstanten matchen willst. Nutze punktierte Namen (
Color.RED). - Testen mit parametrisierten Fällen: Für umfangreiche Match-Logik Tests mit vielen Beispielen schreiben.
Häufige Stolpersteine (und wie du sie vermeidest)
1) Capture vs. Konstante
Das hier hält nicht nach einer Konstante Ausschau, sondern bindet:
# FALSCH, "RED" wird als Variable gebunden (Capture)
case RED:
...
So machst du es richtig:
# RICHTIG: punktierter Name
case Color.RED:
...
2) Doppeltes Binden desselben Namens im selben Muster
Diese Schreibweise ist ein SyntaxError (du kannst nicht implizit auf Gleichheit testen):
# FALSCH
case [x, x]:
...
Nutze stattdessen einen Guard:
# RICHTIG
case [a, b] if a == b:
...
3) Default-Fall vergessen
Wenn du nicht alle Fälle explizit abdeckst, setze case _: ans Ende, um Unerwartetes abzufangen.
4) Falsche Reihenfolge
Plaziere spezifische Fälle vor allgemeineren, sonst „verschluckt“ ein generischer Case die nachfolgenden.
5) Strings zerlegen
Strings werden nicht als Sequenzen im Sinne der Muster behandelt. Wenn du Zeichen prüfen willst, arbeite mit Guards oder expliziten Checks:
def char_type(s):
match s:
case str() if len(s) == 1 and s.isdigit():
return "digit"
case str() if len(s) == 1 and s.isalpha():
return "letter"
case _:
return "other"
6) Variablen-Leaking verstehen
Variablen, die im Case gebunden werden, existieren im Scope des umgebenden Blocks. Guards, die fehlschlagen, binden nicht. Nutze sprechende Namen und kapsle Logik in Funktionen, wenn nötig.
Leistungsaspekte und Wartbarkeit
- Performance: match-case ist effizient implementiert, aber sein Hauptvorteil ist Lesbarkeit und Wartbarkeit, nicht Micro-Optimierung.
- Vergleiche: Dict-Dispatch ist für einfache Wert-zu-Funktion-Lookups sehr schnell. match-case gewinnt, sobald Struktur und Bedingungen zählen.
- Refactoring: Muster sind selbstdokumentierend. Das reduziert Kommentare und erleichtert Reviews.
Testing, Typing und Exaktheit
Für exakte Fallabdeckung lohnt sich der Einsatz von Enums und Typ-Checkern:
from enum import Enum
class Op(Enum):
ADD = "add"
SUB = "sub"
MUL = "mul"
def calc(op: Op, a: int, b: int) -> int:
match op:
case Op.ADD:
return a + b
case Op.SUB:
return a - b
case Op.MUL:
return a * b
case _:
raise ValueError("Unknown operation")
- Exhaustivität: Durch Enums kann ein Typchecker dir helfen, fehlende Fälle zu identifizieren, wenn du versehentlich einen Wert nicht behandelst.
- Property-Tests: Für komplexe Datenstrukturen lohnt sich Hypothesis o. ä., um viele Musterfälle automatisch zu generieren.
Tipps für Migrationspfade und Versionen
- Version prüfen: match-case ist ab Python 3.10 verfügbar. Für ältere Versionen bleib bei if-elif-else oder Dict-Dispatch.
- Soft Keywords:
matchundcasesind „soft keywords“ – vermeide sie dennoch als Variable, um Verwirrung zu vermeiden. - Schrittweise Migration: Ersetze zunächst Stellen, an denen Daten dekonstruieren wirklich hilft (JSON, AST, Kommandoparser).
Do’s und Don’ts
- Do: Enums, literale Werte, Guards, As-Bindungen sinnvoll einsetzen.
- Do: Spezifische Cases vor generischen anordnen,
case _nicht vergessen. - Do: Tests schreiben, besonders bei verschachtelten Mustern und Guards.
- Don’t: Zu komplexe Muster in einem Case packen – splitte in mehrere Cases.
- Don’t: Konstante Namen ohne Punkt verwenden (Capture-Falle!).
- Don’t:
match-caseals generellen Ersatz für alles nutzen – bei einfachen Checks bleibenif/elifideal.
Komplettes Beispiel: Robuste Event-Pipeline
Ein kompaktes Beispiel, das Sequenz-, Mapping- und Klassenmuster sowie Guards kombiniert:
from dataclasses import dataclass
from typing import Any
@dataclass
class User:
id: int
name: str
active: bool
__match_args__ = ("id", "name", "active")
def handle(event: Any) -> str:
match event:
case {"kind": "user", "payload": {"id": uid, "name": name, "active": True}}:
return f"Active user {name} ({uid})"
case {"kind": "user", "payload": {"id": uid, "name": name, "active": False}}:
return f"Inactive user {name} ({uid})"
case {"kind": "user", "payload": User(uid, name, True)}:
return f"Active User dataclass {name} ({uid})"
case {"kind": "order", "payload": [first, *rest]} as full if len(full["payload"]) > 0:
return f"Order with {1 + len(rest)} lines"
case ["metric", {"name": n, "value": v}] if isinstance(v, (int, float)):
return f"Metric {n}={v}"
case _:
return "Unknown event"
Fazit
match-case ist die moderne Antwort auf den lang vermissten „python switch case“. Es ist kein 1:1-Ersatz der klassischen Switch-Logik, sondern eine stärkere, deklarative Form der Fallunterscheidung. Du kannst nicht nur auf konkrete Werte, sondern auf Strukturen matchen: Sequenzen, Mappings, Klasseninstanzen – inklusive Dekonstruktion, Guards und OR-Muster. Das Ergebnis ist meist kompakter, lesbarer und wartbarer Code, insbesondere bei verschachtelten Daten und unterschiedlichen Formen derselben Entität. Nutze weiterhin if-elif-else für triviale Fälle und Dictionary-Dispatch für reine Wert-zu-Funktion-Zuordnungen. Bei allem, was darüber hinausgeht, ist match-case die richtige Wahl.
FAQ
Gibt es in Python ein klassisches Switch-Case?
Nein. Python hat nie ein klassisches Switch-Case mit break-Pflicht. Seit 3.10 gibt es match-case als strukturelles Musterabgleichen – mächtiger als ein traditionelles Switch.
Was ist der Unterschied zwischen match-case und if-elif-else?
if-elif-else ist imperativ und arbeitet mit Bedingungen. match-case ist deklarativ und beschreibt die Form (Struktur) der Daten. Für komplexe Daten ist match-case oft deutlich klarer.
Kann ich mit match-case Listen und Dictionaries matchen?
Ja. Sequenzmuster ([x, y], [x, *rest]) und Mappingmuster ({"key": value, **rest}) sind zentrale Stärken von match-case.
Wie setze ich einen Default-Fall um?
Mit case _:. Das Unterstrich-Muster passt auf alles und bindet nichts.
Gibt es Fallthrough wie bei C oder Java?
Nein. Das erste passende Muster gewinnt, danach ist der Match-Block beendet. Du brauchst kein break.
Wie vermeide ich die Capture-Falle bei Konstanten?
Nutze punktierte Namen (z. B. Color.RED) oder kapsle Konstanten in einem Namespace/Enum. Ein einzelner Name wie RED wird als Capture interpretiert.
Kann ich Strings als Sequenzen matchen?
Nein, Strings werden in Muster nicht wie Listen/Tupel dekonstruierbar behandelt. Prüfe stattdessen mit Guards oder expliziten Bedingungen.
Gibt es Unterstützung durch Typchecker?
Ja. Mit Enums und Typannotationen können Typchecker helfen, fehlende Fälle zu finden (Exhaustivität). Das erhöht die Robustheit.
Ist match-case schneller als if-elif?
Kommt darauf an. Bei simplen Checks ist if-elif oder Dict-Dispatch nicht selten schneller. Der große Vorteil von match-case ist jedoch die Lesbarkeit und Wartbarkeit bei komplexen Strukturen.
Was mache ich in älteren Python-Versionen?
Bleibe bei if-elif-else oder Dictionary-Dispatch. Für die meisten Muster lohnt sich das Upgrade auf Python 3.10+.
Wie oft sollte ich das Keyword „python switch case“ im Text verwenden?
Organisch und sparsam. Der Kern ist match-case; nutze „python switch case“, wenn du den konzeptionellen Bezug zum klassischen Switch herstellst, ohne zu übertreiben.

