Skip to content
Themen
Python
Python Switch Case: match-case-Anweisung erklärt

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: hello

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

FeaturePython match-caseC/Java switch-case
Fall-throughNein (jeder Fall ist unabhängig)Ja (erfordert break)
Pattern-TypenLiterale, Sequenzen, Mappings, Klassen, OR, GuardsMeist nur Literale
DestructuringIntegriert (bindet Variablen aus der Struktur)Nicht unterstützt
VariablenbindungErfasst Werte aus dem passenden PatternNicht unterstützt
Standardfallcase _:default:
Ausdruck/AnweisungAnweisung (kein Rückgabewert)Anweisung
Minimales Python3.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 Found

2. 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: 42

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

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

Guards 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 exceeded

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

Alternativen 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))  # inf

Enum-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?

AnsatzPython-VersionAm besten fürKomplexitätPerformance
match-case3.10+Komplexe Patterns, Destructuring, Typ-MatchingHohe AusdruckskraftVergleichbar mit if/elif
if/elif/elseAlleEinfache Wertvergleiche, < 6 FälleEinfachO(n) linearer Durchlauf
Dict dispatchAlleViele Fälle, Wert → Funktions-MappingMittelO(1)-Lookup
Dict + lambdaAlleViele Fälle, Wert → Ausdruck-MappingMittelO(1)-Lookup
Enum dispatchAlleTypisierte Konstanten, exhaustives MatchingMittelO(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)  # 14

Zustandsmaschine 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
ApproachBest 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":
        pass

Prü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 logs

Hä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

📚