Python Switch Case: match-case-Anweisung erklärt
Aktualisiert am
Du schreibst in Python einen Befehlsprozessor. Die Funktion nimmt einen String wie "quit", "save", "load" oder "help" entgegen und muss für jeden Befehl unterschiedliche Logik ausführen. In JavaScript oder C würdest du eine switch-Anweisung verwenden. In Python schreibst du eine Kette aus if/elif/else-Blöcken — fünf Zeilen für zwei Befehle, fünfzehn für sechs, dreißig für zwölf. Die Funktion wird zu einer Wand aus Vergleichen, die mühsam zu lesen, leicht falsch anzuordnen und unangenehm zu erweitern ist.
If your search query is python switch case, start here. For the deeper structural-pattern-matching reference, see Python Match Case. For a simpler dictionary-dispatch overview, see Python Switch Case.
Dies war über 30 Jahre lang eine echte Einschränkung von Python. Jede andere große Sprache hatte ein switch- oder case-Konstrukt, aber Python verließ sich ausschließlich auf if/elif-Ketten und Dictionary-Dispatch. Guido van Rossum lehnte Switch-Case-Vorschläge mehrfach ab und argumentierte, dass if/elif ausreiche.
Das änderte sich in Python 3.10 mit PEP 634 (opens in a new tab), das die match-case-Anweisung einführte — Pythons Antwort auf switch-case, aber deutlich mächtiger. Statt nur Werte zu vergleichen, kann es Objekte zerlegen, Muster abgleichen, Variablen binden und Bedingungen anwenden. Es ist strukturelles Pattern Matching, nicht nur ein Switch-Ersatz.
Quick Syntax: match-case in 30 Sekunden
def handle_command(command):
match command:
case "quit":
print("Exiting program")
case "save":
print("Saving file")
case "help":
print("Showing help")
case _:
print(f"Unknown command: {command}")
handle_command("save") # Saving file
handle_command("hello") # Unknown command: helloDas match-Schlüsselwort wertet den Subject-Ausdruck nur einmal aus. Jedes case definiert ein Pattern. Python prüft die Patterns von oben nach unten und führt den ersten Treffer aus. Der Platzhalter _ ist der Standardfall — er passt auf alles.
match-case vs switch-case: Wichtige Unterschiede
Wenn du von C, Java, JavaScript oder Go kommst, sind hier die entscheidenden Unterschiede:
| Feature | Python match-case | C/Java switch-case |
|---|---|---|
| Fall-through | Nein (jeder Fall ist unabhängig) | Ja (erfordert break) |
| Pattern-Typen | Literale, Sequenzen, Mappings, Klassen, OR, Guards | Meist nur Literale |
| Destructuring | Integriert (bindet Variablen aus der Struktur) | Nicht unterstützt |
| Variablenbindung | Erfasst Werte aus dem passenden Pattern | Nicht unterstützt |
| Standardfall | case _: | default: |
| Ausdruck/Anweisung | Anweisung (kein Rückgabewert) | Anweisung |
| Minimales Python | 3.10+ | N/A |
Der wichtigste Unterschied: kein Fall-through. Jeder Case-Block ist isoliert. Du brauchst nie eine break-Anweisung und führst nie versehentlich den nächsten Case aus.
Pattern-Typen: Was match-case kann
Python's match-case unterstützt sieben Pattern-Typen. Das macht es grundsätzlich mächtiger als eine traditionelle switch-Anweisung.
1. Literal Patterns
Exakte Werte abgleichen — Strings, Zahlen, Booleans, None:
def classify_status(code):
match code:
case 200:
return "OK"
case 301:
return "Moved Permanently"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return f"Unknown status: {code}"
print(classify_status(404)) # Not Found2. OR Patterns (Mehrere Werte)
Verwende den Operator |, um mehrere Werte in einem Case zu matchen:
def classify_http_status(code):
match code:
case 200 | 201 | 204:
return "Success"
case 301 | 302 | 307 | 308:
return "Redirect"
case 400 | 401 | 403 | 404:
return "Client Error"
case 500 | 502 | 503:
return "Server Error"
case _:
return "Unknown"Das ersetzt das häufige Muster if code in (200, 201, 204):.
3. Capture Patterns (Variablenbindung)
Ein nackter Name in einem Case erfasst den passenden Wert in einer Variable:
def process_value(data):
match data:
case 0:
print("Zero")
case value:
print(f"Got non-zero value: {value}")
process_value(42) # Got non-zero value: 42Wichtige Falle: Ein nackter Name bindet immer — er vergleicht nicht mit einer vorhandenen Variable. Das ist der häufigste Fehler:
# WRONG — this does NOT compare against expected_code
expected_code = 200
match response_code:
case expected_code: # This captures response_code into expected_code!
print("Match")
# RIGHT — use a guard or literal
match response_code:
case code if code == expected_code:
print("Match")Um gegen Konstanten zu matchen, verwende punktierte Namen (wie http.HTTPStatus.OK) oder Guard-Klauseln.
4. Sequence Patterns (Listen und Tupel)
Zerlege Listen und Tupel direkt im Pattern:
def process_point(point):
match point:
case (0, 0):
return "Origin"
case (x, 0):
return f"On x-axis at {x}"
case (0, y):
return f"On y-axis at {y}"
case (x, y):
return f"Point at ({x}, {y})"
print(process_point((3, 0))) # On x-axis at 3
print(process_point((2, 5))) # Point at (2, 5)Du kannst *rest verwenden, um verbleibende Elemente zu erfassen:
def first_and_rest(items):
match items:
case []:
return "Empty list"
case [single]:
return f"Single item: {single}"
case [first, *rest]:
return f"First: {first}, remaining: {rest}"
print(first_and_rest([1, 2, 3, 4])) # First: 1, remaining: [2, 3, 4]5. Mapping Patterns (Dictionaries)
Vergleiche Dictionary-Keys und zerlege Werte:
def handle_event(event):
match event:
case {"type": "click", "x": x, "y": y}:
print(f"Click at ({x}, {y})")
case {"type": "keypress", "key": key}:
print(f"Key pressed: {key}")
case {"type": "scroll", "direction": direction}:
print(f"Scroll {direction}")
case _:
print("Unknown event")
handle_event({"type": "click", "x": 100, "y": 200})
# Click at (100, 200)
handle_event({"type": "keypress", "key": "Enter", "modifiers": ["Ctrl"]})
# Key pressed: Enter (extra keys are ignored)Mapping-Patterns matchen, wenn die angegebenen Keys vorhanden sind — zusätzliche Keys im Dictionary werden stillschweigend ignoriert. Das macht sie ideal für die Verarbeitung von JSON-Daten oder API-Antworten.
6. Class Patterns
Vergleiche gegen Instanzen von Klassen und extrahiere Attribute:
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Circle:
center: Point
radius: float
@dataclass
class Rectangle:
corner: Point
width: float
height: float
def describe_shape(shape):
match shape:
case Circle(center=Point(x=0, y=0), radius=r):
return f"Circle at origin with radius {r}"
case Circle(center=center, radius=r):
return f"Circle at ({center.x}, {center.y}) with radius {r}"
case Rectangle(width=w, height=h) if w == h:
return f"Square with side {w}"
case Rectangle(width=w, height=h):
return f"Rectangle {w}x{h}"
print(describe_shape(Circle(Point(0, 0), 5)))
# Circle at origin with radius 5
print(describe_shape(Rectangle(Point(1, 1), 4, 4)))
# Square with side 47. Guard Clauses
Füge jedem Pattern if-Bedingungen hinzu, um zusätzlich zu filtern:
def classify_number(n):
match n:
case n if n < 0:
return "Negative"
case 0:
return "Zero"
case n if n % 2 == 0:
return "Positive even"
case _:
return "Positive odd"
print(classify_number(-5)) # Negative
print(classify_number(4)) # Positive even
print(classify_number(7)) # Positive oddGuards erlauben beliebige Bedingungen über das hinaus, was Patterns ausdrücken können. Zuerst wird das Pattern gematcht, danach wird die Guard-Bedingung ausgewertet.
Praxisbeispiel: JSON-API-Response-Parser
Eine der praktischsten Anwendungen von match-case ist das Parsen von API-Antworten oder JSON-Daten:
import json
def process_api_response(response: dict) -> str:
match response:
case {"status": "success", "data": {"users": [*users]}}:
return f"Found {len(users)} users"
case {"status": "success", "data": {"user": {"name": name, "email": email}}}:
return f"User: {name} ({email})"
case {"status": "error", "code": 401}:
return "Authentication required"
case {"status": "error", "code": code, "message": msg}:
return f"Error {code}: {msg}"
case {"status": "error"}:
return "Unknown error"
case _:
return "Unrecognized response format"
# Test with different responses
print(process_api_response({
"status": "success",
"data": {"users": ["Alice", "Bob", "Charlie"]}
}))
# Found 3 users
print(process_api_response({
"status": "error",
"code": 429,
"message": "Rate limit exceeded"
}))
# Error 429: Rate limit exceededDas ersetzt tief verschachtelte if/elif-Ketten mit isinstance()-Prüfungen und Dictionary-.get()-Aufrufen. Die match-case-Version beschreibt die erwartete Struktur direkt.
Praxisbeispiel: Parser für Kommandozeilenargumente
import sys
def parse_args(args: list[str]):
match args:
case [program]:
print(f"Usage: {program} <command> [options]")
case [_, "init", project_name]:
print(f"Initializing project: {project_name}")
case [_, "init"]:
print("Error: project name required")
case [_, "build", "--release"]:
print("Building in release mode")
case [_, "build"]:
print("Building in debug mode")
case [_, "test", *test_files] if test_files:
print(f"Running tests: {', '.join(test_files)}")
case [_, "test"]:
print("Running all tests")
case [_, unknown, *_]:
print(f"Unknown command: {unknown}")
parse_args(["app", "init", "my-project"]) # Initializing project: my-project
parse_args(["app", "test", "a.py", "b.py"]) # Running tests: a.py, b.py
parse_args(["app", "build", "--release"]) # Building in release modeAlternativen vor 3.10: Switch-Case ohne match-case
Wenn du Python 3.9 oder älter verwendest oder match-case für einfaches Value-Matching zu komplex ist, sind das die etablierten Alternativen.
if/elif/else-Ketten
Der naheliegendste Ansatz:
def handle_command(command):
if command == "quit":
print("Exiting program")
elif command == "save":
print("Saving file")
elif command == "load":
print("Loading file")
elif command == "help":
print("Showing help")
else:
print(f"Unknown command: {command}")Wann verwenden: Wenn du weniger als 5–6 Cases und einfache Logik hast.
Dictionary-Dispatch
Werte mithilfe eines Dictionaries auf Funktionen abbilden:
def cmd_quit():
print("Exiting program")
def cmd_save():
print("Saving file")
def cmd_help():
print("Showing help")
commands = {
"quit": cmd_quit,
"save": cmd_save,
"help": cmd_help,
}
def handle_command(command):
action = commands.get(command)
if action:
action()
else:
print(f"Unknown command: {command}")Wann verwenden: Wenn du Werte auf einfache Aktionen abbildest, besonders bei vielen Fällen. O(1)-Lookup vs. O(n) bei if/elif.
Dictionary-Dispatch mit Argumenten
def calculate(operation, a, b):
ops = {
"+": lambda a, b: a + b,
"-": lambda a, b: a - b,
"*": lambda a, b: a * b,
"/": lambda a, b: a / b if b != 0 else float("inf"),
}
func = ops.get(operation)
if func is None:
raise ValueError(f"Unknown operation: {operation}")
return func(a, b)
print(calculate("+", 10, 3)) # 13
print(calculate("/", 10, 0)) # infEnum-basiertes Dispatch
Für typsicheren, selbsterklärenden Code:
from enum import Enum
class Direction(Enum):
NORTH = "north"
SOUTH = "south"
EAST = "east"
WEST = "west"
def move(direction: Direction, x: int, y: int) -> tuple[int, int]:
offsets = {
Direction.NORTH: (0, 1),
Direction.SOUTH: (0, -1),
Direction.EAST: (1, 0),
Direction.WEST: (-1, 0),
}
dx, dy = offsets[direction]
return (x + dx, y + dy)
print(move(Direction.NORTH, 0, 0)) # (0, 1)Vergleich: Welche Methode sollte man verwenden?
| Ansatz | Python-Version | Am besten für | Komplexität | Performance |
|---|---|---|---|---|
match-case | 3.10+ | Komplexe Patterns, Destructuring, Typ-Matching | Hohe Ausdruckskraft | Vergleichbar mit if/elif |
if/elif/else | Alle | Einfache Wertvergleiche, < 6 Fälle | Einfach | O(n) linearer Durchlauf |
| Dict dispatch | Alle | Viele Fälle, Wert → Funktions-Mapping | Mittel | O(1)-Lookup |
| Dict + lambda | Alle | Viele Fälle, Wert → Ausdruck-Mapping | Mittel | O(1)-Lookup |
| Enum dispatch | Alle | Typisierte Konstanten, exhaustives Matching | Mittel | O(1)-Lookup |
Faustregel:
- Verwende match-case, wenn du Destructuring, Typ-Matching oder komplexe Patterns brauchst
- Verwende if/elif für einfache Vergleiche mit wenigen Zweigen
- Verwende dict dispatch für viele einfache Wert-zu-Aktion-Zuordnungen
- Verwende enum dispatch, wenn die Menge der Werte fest und typisiert ist
Häufige Patterns und Rezepte
Typbasiertes Dispatch
def serialize(value):
match value:
case bool(): # Must come before int — bool is a subclass of int
return "true" if value else "false"
case int() | float():
return str(value)
case str():
return f'"{value}"'
case list():
items = ", ".join(serialize(v) for v in value)
return f"[{items}]"
case dict():
pairs = ", ".join(
f'{serialize(k)}: {serialize(v)}' for k, v in value.items()
)
return "{" + pairs + "}"
case None:
return "null"
case _:
raise TypeError(f"Cannot serialize {type(value)}")
print(serialize({"name": "Alice", "scores": [95, 87, 92], "active": True}))
# {"name": "Alice", "scores": [95, 87, 92], "active": true}Wichtig: Setze bool() vor int(), weil True und False in Python Instanzen von int sind.
Verschachteltes Pattern Matching
def evaluate(expr):
"""Simple math expression evaluator."""
match expr:
case int(n) | float(n):
return n
case ("+", left, right):
return evaluate(left) + evaluate(right)
case ("-", left, right):
return evaluate(left) - evaluate(right)
case ("*", left, right):
return evaluate(left) * evaluate(right)
case ("/", left, right):
divisor = evaluate(right)
if divisor == 0:
raise ZeroDivisionError("Division by zero")
return evaluate(left) / divisor
case ("neg", operand):
return -evaluate(operand)
case _:
raise ValueError(f"Invalid expression: {expr}")
# (3 + 4) * 2
result = evaluate(("*", ("+", 3, 4), 2))
print(result) # 14Zustandsmaschine mit match-case
def tokenize(text):
"""Simple tokenizer using match-case as a state machine."""
tokens = []
i = 0
while i < len(text):
match text[i]:
case ' ' | '\t' | '\n':
i += 1 # Skip whitespace
case '+' | '-' | '*' | '/':
tokens.append(("OP", text[i]))
i += 1
case '(' | ')':
tokens.append(("PAREN", text[i]))
i += 1
case c if c.isdigit():
j = i
while j < len(text) and text[j].isdigit():
j += 1
tokens.append(("NUM", int(text[i:j])))
i = j
case c if c.isalpha():
j = i
while j < len(text) and text[j].isalnum():
j += 1
tokens.append(("ID", text[i:j]))
i = j
case c:
raise SyntaxError(f"Unexpected character: {c}")
return tokens
print(tokenize("x + 42 * (y - 3)"))
# [('ID', 'x'), ('OP', '+'), ('NUM', 42), ('OP', '*'), ('PAREN', '('),
# ('ID', 'y'), ('PAREN', ')')]Performance: match-case vs if/elif vs dict
Bei einfachem Value-Matching sind alle drei Ansätze schnell genug, dass der Unterschied in der Praxis keine Rolle spielt. Hier sind Benchmark-Ergebnisse für das Matching gegen 10 String-Werte:
import timeit
commands = ["cmd_0", "cmd_1", "cmd_2", "cmd_3", "cmd_4",
"cmd_5", "cmd_6", "cmd_7", "cmd_8", "cmd_9"]
# if/elif approach
def if_elif_dispatch(cmd):
if cmd == "cmd_0": return 0
elif cmd == "cmd_1": return 1
elif cmd == "cmd_2": return 2
elif cmd == "cmd_3": return 3
elif cmd == "cmd_4": return 4
elif cmd == "cmd_5": return 5
elif cmd == "cmd_6": return 6
elif cmd == "cmd_7": return 7
elif cmd == "cmd_8": return 8
elif cmd == "cmd_9": return 9
else: return -1
# dict dispatch approach
dispatch_dict = {f"cmd_{i}": i for i in range(10)}
def dict_dispatch(cmd):
return dispatch_dict.get(cmd, -1)
# match-case approach
def match_dispatch(cmd):
match cmd:
case "cmd_0": return 0
case "cmd_1": return 1
case "cmd_2": return 2
case "cmd_3": return 3
case "cmd_4": return 4
case "cmd_5": return 5
case "cmd_6": return 6
case "cmd_7": return 7
case "cmd_8": return 8
case "cmd_9": return 9
case _: return -1| Approach | Best case (first match) | Worst case (last match) | Miss (no match) |
|---|---|---|---|
| if/elif | ~80ns | ~400ns | ~420ns |
| dict dispatch | ~60ns | ~60ns | ~65ns |
| match-case | ~90ns | ~450ns | ~470ns |
Wichtigste Erkenntnis: Dictionary-Dispatch hat O(1)-konstante Zeit unabhängig von der Position. Sowohl if/elif als auch match-case sind O(n) lineare Scans. Bei weniger als etwa 20 Fällen ist der Unterschied vernachlässigbar. Wähle nach Lesbarkeit, nicht nach Performance.
Häufige Fehler und wie man sie vermeidet
Fehler 1: match-case vor Python 3.10 verwenden
# This causes SyntaxError in Python 3.9 and earlier
match command: # SyntaxError: invalid syntax
case "quit":
passPrüfe deine Python-Version: python --version. Wenn du Python 3.9 oder älter verwendest, nutze if/elif oder Dict-Dispatch.
Fehler 2: Gegen Variablen vergleichen
# WRONG — captures, does NOT compare
STATUS_OK = 200
match response_code:
case STATUS_OK: # This always matches and overwrites STATUS_OK!
print("OK")
# RIGHT — use dotted name
class Status:
OK = 200
NOT_FOUND = 404
match response_code:
case Status.OK:
print("OK")
case Status.NOT_FOUND:
print("Not Found")
# RIGHT — use a guard
match response_code:
case code if code == STATUS_OK:
print("OK")Fehler 3: Den Wildcard-Standardfall vergessen
# DANGEROUS — unmatched values silently do nothing
match command:
case "save":
save_file()
case "load":
load_file()
# No default — "delete" silently falls through with no action
# SAFE — always include a default case
match command:
case "save":
save_file()
case "load":
load_file()
case _:
raise ValueError(f"Unknown command: {command}")Fehler 4: Falsche Reihenfolge bei überlappenden Patterns
# WRONG — the first case catches everything
match value:
case x: # Captures ANY value — always matches!
print(f"Got: {x}")
case 42: # Never reached
print("The answer")
# RIGHT — specific patterns first, general last
match value:
case 42:
print("The answer")
case x:
print(f"Got: {x}")Using match-case in Data Science
Wenn du mit Datenverarbeitungspipelines arbeitest, ist match-case nützlich für das Behandeln unterschiedlicher Datenformate und Bereinigungsoperationen:
import csv
from pathlib import Path
def load_data(source):
"""Load data from different source types."""
match source:
case str() as path if path.endswith(".csv"):
with open(path) as f:
return list(csv.DictReader(f))
case str() as path if path.endswith(".json"):
import json
with open(path) as f:
return json.load(f)
case list() as records:
return records
case dict() as single_record:
return [single_record]
case _:
raise TypeError(f"Unsupported data source: {type(source)}")Für interaktive Datenexploration ermöglichen Tools wie PyGWalker (opens in a new tab), DataFrames direkt zu visualisieren, ohne Plotting-Code zu schreiben — nützlich, wenn du schnell die Daten prüfen möchtest, die deine Verarbeitungspipeline erzeugt.
Wenn du Datenverarbeitungsskripte baust, die match-case für komplexe Verzweigungen verwenden, bietet RunCell (opens in a new tab) eine KI-gestützte Jupyter-Umgebung, in der du Pattern Matching interaktiv mit realen Datenbeispielen testen kannst.
match-case mit Dataclasses und Named Tuples
Python's match-case funktioniert besonders gut mit Dataclasses und Named Tuples:
from dataclasses import dataclass
from typing import Optional
@dataclass
class LogEntry:
level: str
message: str
error: Optional[Exception] = None
def handle_log(entry: LogEntry):
match entry:
case LogEntry(level="CRITICAL", error=err) if err is not None:
send_alert(f"CRITICAL with error: {err}")
restart_service()
case LogEntry(level="CRITICAL", message=msg):
send_alert(f"CRITICAL: {msg}")
case LogEntry(level="ERROR", error=err) if err is not None:
log_to_file(f"ERROR: {err}")
case LogEntry(level="WARNING" | "ERROR", message=msg):
log_to_file(msg)
case LogEntry(level="INFO" | "DEBUG"):
pass # Ignore low-priority logsHäufig gestellte Fragen
Does Python have a switch statement?
Python 3.10+ has the match-case statement, which is Python's version of switch-case. It goes beyond traditional switch with structural pattern matching — you can destructure objects, match patterns, and bind variables. For Python 3.9 and earlier, use if/elif/else chains or dictionary dispatch as alternatives.
What is the difference between match-case and switch-case?
Python's match-case has no fall-through (no break needed), supports pattern matching (sequences, mappings, classes), can destructure and bind variables from matched patterns, and supports guard clauses with if conditions. Traditional switch-case in C/Java only matches literal values.
Is match-case faster than if/elif in Python?
For simple value matching, they perform similarly — both scan linearly. Dictionary dispatch is faster (O(1)) for many cases. The performance difference is negligible for fewer than 20 cases. Choose based on readability and pattern complexity, not performance.
Can I use match-case with Python 3.9?
No. The match-case statement requires Python 3.10 or later. For earlier versions, use if/elif/else chains or dictionary dispatch.
Why does my match-case variable comparison not work?
A bare name like case x: captures any value into x — it does not compare against an existing variable. To compare against a variable, use a guard clause (case val if val == x:) or dotted names (case MyClass.CONSTANT:).
Was ist _ in match-case?
Das _-Pattern ist der Wildcard-Fall. Es matcht alles, ohne einen Wert an eine Variable zu binden. Es ist der Standardfall — wie default: in Switch-Anweisungen. Platziere es immer zuletzt.
Verwandte Anleitungen
- Python Try Except: Error Handling Guide
- Python Type Hints: Complete Tutorial
- Python Decorators Explained
- Python Collections Module Guide
- Python Enumerate Function
- Python Assert Statement