Skip to content

Python Match Case: Strukturelles Pattern Matching erklärt (Python 3.10+)

Updated on

Python-Entwickler haben sich jahrzehntelang auf if-elif-Ketten verlassen, um Verzweigungslogik umzusetzen. Eine Variable gegen mehrere Werte zu prüfen, die Struktur von Daten zu inspizieren oder abhängig von Objekttypen zu routen, erfordert oft ausführliche Bedingungsblöcke, die mit zunehmender Anzahl an Fällen immer schwerer zu lesen sind. Eine Funktion, die auf zehn mögliche Command-Typen dispatcht, wird schnell zur Wand aus elif-Statements, in der die eigentliche Logik unter repetitiven Vergleichen begraben ist.

Das ist nicht nur ein Lesbarkeitsproblem. Tiefe if-elif-Ketten sind anfällig für subtile Bugs – eine falsch platzierte Bedingung, ein vergessener Fall oder ein unbeabsichtigtes „Durchrutschen“, das stillschweigend die falschen Daten weitergibt. Andere Sprachen haben das seit Jahren mit Pattern-Matching-Konstrukten gelöst, aber Python hatte bis Version 3.10 keine native Lösung.

Python 3.10 führte das match-case-Statement (PEP 634) ein und brachte damit strukturelles Pattern Matching in die Sprache. Es geht weit über einfachen Wertvergleich hinaus: Du kannst Sequenzen destrukturieren, Dictionary-Formen matchen, Variablen binden, Guard-Conditions anwenden und nach Klassen-Typen dispatchen – alles in einer sauberen, deklarativen Syntax. Dieser Guide behandelt jeden Pattern-Typ mit praktischen Beispielen.

📚

Was ist strukturelles Pattern Matching?

Strukturelles Pattern Matching erlaubt dir, einen Wert (das „Subject“) mit einer Reihe von Patterns zu vergleichen und den Codeblock des ersten passenden Patterns auszuführen. Anders als ein switch-Statement in C oder Java vergleicht Pythons match-case nicht nur Werte – es untersucht die Struktur der Daten.

Die grundlegende Syntax:

match subject:
    case pattern1:
        # code for pattern1
    case pattern2:
        # code for pattern2
    case _:
        # default case (wildcard)

Wichtige Eigenschaften:

  • Patterns werden von oben nach unten geprüft. Das erste passende Pattern gewinnt.
  • Das Wildcard-Pattern _ matcht alles und dient als Default-Case.
  • Patterns können Variablen binden, d. h. Teile des gematchten Werts werden in Namen eingefangen, die du im Case-Block verwenden kannst.
  • Kein Fall-through-Verhalten. Es wird nur ein Case-Block ausgeführt.

Python-Versionsanforderung: match-case erfordert Python 3.10 oder neuer. Prüfe deine Version mit python --version. Wenn du eine ältere Version verwendest, musst du upgraden, bevor du dieses Feature nutzen kannst.

Literal Patterns: Exakte Werte matchen

Die einfachste Verwendung von match-case vergleicht einen Wert mit literalen Konstanten:

def get_http_status_message(code):
    match code:
        case 200:
            return "OK"
        case 201:
            return "Created"
        case 301:
            return "Moved Permanently"
        case 400:
            return "Bad Request"
        case 403:
            return "Forbidden"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return f"Unknown status code: {code}"
 
print(get_http_status_message(404))  # Not Found
print(get_http_status_message(418))  # Unknown status code: 418

Literal Patterns funktionieren mit Integern, Strings, Booleans und None:

def describe_value(value):
    match value:
        case True:
            return "Boolean true"
        case False:
            return "Boolean false"
        case None:
            return "None value"
        case "":
            return "Empty string"
        case 0:
            return "Zero"
        case _:
            return f"Other: {value}"

Wichtig: True, False und None werden per Identität gematcht (wie is), nicht per Gleichheit. Das bedeutet: match 1 matcht nicht case True, auch wenn 1 == True in Python True ist.

Or-Patterns: Mehrere Werte matchen

Verwende den Pipe-Operator |, um in einem einzelnen Case eines von mehreren Patterns zu matchen:

def classify_day(day):
    match day.lower():
        case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
            return "Weekday"
        case "saturday" | "sunday":
            return "Weekend"
        case _:
            return "Invalid day"
 
print(classify_day("Saturday"))  # Weekend
print(classify_day("Wednesday")) # Weekday

Or-Patterns funktionieren mit jedem Pattern-Typ, nicht nur mit Literalen:

def categorize_error(code):
    match code:
        case 400 | 401 | 403 | 404 | 405:
            return "Client error"
        case 500 | 502 | 503 | 504:
            return "Server error"
        case _:
            return "Other"

Variable Capture Patterns

Patterns können Teile des gematchten Werts in Variablen capturen. Ein „nackter“ Name in einem Pattern fungiert als Variable, die an jeden Wert gebunden wird, der an dieser Position steht:

def parse_command(command):
    match command.split():
        case ["quit"]:
            return "Exiting program"
        case ["hello", name]:
            return f"Hello, {name}!"
        case ["add", x, y]:
            return f"Sum: {int(x) + int(y)}"
        case _:
            return "Unknown command"
 
print(parse_command("hello Alice"))  # Hello, Alice!
print(parse_command("add 3 5"))      # Sum: 8
print(parse_command("quit"))         # Exiting program

In case ["hello", name] fängt die Variable name den String ein, der an zweiter Position in der Liste steht. Das ist fundamental anders als Literal Matching – es gibt keine Variable namens name, gegen die verglichen wird. Stattdessen erstellt das Pattern eine neue Bindung.

Die Capture-vs-Constant-Falle

Da „nackte“ Namen Capture-Patterns sind, kannst du nicht direkt gegen eine Variable matchen:

QUIT_COMMAND = "quit"
 
match user_input:
    case QUIT_COMMAND:  # This captures into QUIT_COMMAND, not compare!
        print("This always matches!")

Um gegen eine Konstante zu matchen, verwende dotted names oder literale Werte:

class Commands:
    QUIT = "quit"
    HELP = "help"
 
match user_input:
    case Commands.QUIT:  # Dotted name: compares against the value
        print("Quitting")
    case Commands.HELP:
        print("Showing help")

Dotted names (mit einem .) werden als Value Lookup behandelt, nicht als Capture Pattern. Das ist eine bewusste Designentscheidung in PEP 634.

Sequence Patterns: Listen und Tupel destrukturieren

Match-case ist besonders stark beim Destrukturieren von Sequenzen. Du kannst Länge und Inhalte von Listen oder Tupeln matchen:

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})"
        case _:
            return "Not a valid point"
 
print(process_point((0, 0)))   # Origin
print(process_point((5, 0)))   # On x-axis at 5
print(process_point((3, 7)))   # Point at (3, 7)

Star Patterns für Sequenzen variabler Länge

Verwende *, um Sequenzen variabler Länge zu matchen:

def analyze_sequence(seq):
    match seq:
        case []:
            return "Empty sequence"
        case [single]:
            return f"Single element: {single}"
        case [first, second]:
            return f"Pair: {first}, {second}"
        case [first, *middle, last]:
            return f"First: {first}, Last: {last}, Middle has {len(middle)} items"
 
print(analyze_sequence([]))              # Empty sequence
print(analyze_sequence([42]))            # Single element: 42
print(analyze_sequence([1, 2, 3, 4, 5])) # First: 1, Last: 5, Middle has 3 items

Verschachtelte Sequence Patterns

Patterns können verschachtelt werden, um komplexe Datenstrukturen zu matchen:

def process_matrix_row(row):
    match row:
        case [[a, b], [c, d]]:
            return f"2x2 block: {a}, {b}, {c}, {d}"
        case [first_row, *rest]:
            return f"First row: {first_row}, remaining rows: {len(rest)}"
 
print(process_matrix_row([[1, 2], [3, 4]]))
# 2x2 block: 1, 2, 3, 4

Mapping Patterns: Dictionaries matchen

Mapping Patterns matchen dictionary-ähnliche Objekte, indem sie bestimmte Keys prüfen und optional ihre Values capturen:

def handle_api_response(response):
    match response:
        case {"status": "success", "data": data}:
            return f"Success! Data: {data}"
        case {"status": "error", "message": msg}:
            return f"Error: {msg}"
        case {"status": "error", "code": code, "message": msg}:
            return f"Error {code}: {msg}"
        case {"status": status}:
            return f"Unknown status: {status}"
        case _:
            return "Invalid response format"
 
print(handle_api_response({"status": "success", "data": [1, 2, 3]}))
# Success! Data: [1, 2, 3]
 
print(handle_api_response({"status": "error", "message": "Not found"}))
# Error: Not found

Mapping Patterns matchen, wenn das Dictionary die angegebenen Keys enthält – zusätzliche Keys werden ignoriert. Das macht sie ideal für JSON-Daten und API-Responses.

Nutze **rest, um übrige Keys zu capturen:

def extract_config(config):
    match config:
        case {"host": host, "port": port, **rest}:
            return f"Server: {host}:{port}, extra config: {rest}"
        case _:
            return "Missing required config"
 
print(extract_config({"host": "localhost", "port": 8080, "debug": True}))
# Server: localhost:8080, extra config: {'debug': True}

Guard Clauses: Bedingungen zu Patterns hinzufügen

Guards fügen eine if-Bedingung hinzu, die wahr sein muss, damit das Pattern matcht. Zuerst wird das Pattern geprüft; wenn es matcht, wird der Guard-Ausdruck ausgewertet:

def classify_number(n):
    match n:
        case x if x < 0:
            return "Negative"
        case 0:
            return "Zero"
        case x if x % 2 == 0:
            return "Positive even"
        case x if x % 2 == 1:
            return "Positive odd"
 
print(classify_number(-5))  # Negative
print(classify_number(0))   # Zero
print(classify_number(4))   # Positive even
print(classify_number(7))   # Positive odd

Guards sind essenziell, wenn das Pattern allein die vollständige Matching-Bedingung nicht ausdrücken kann:

def validate_age(data):
    match data:
        case {"name": name, "age": age} if age < 0:
            return f"Invalid: {name} has negative age"
        case {"name": name, "age": age} if age < 18:
            return f"{name} is a minor (age {age})"
        case {"name": name, "age": age} if age >= 18:
            return f"{name} is an adult (age {age})"
        case _:
            return "Invalid data format"
 
print(validate_age({"name": "Alice", "age": 25}))
# Alice is an adult (age 25)

Class Patterns: Objekttypen matchen

Class Patterns prüfen, ob ein Wert eine Instanz einer Klasse ist, und können optional deren Attribute destrukturieren:

from dataclasses import dataclass
 
@dataclass
class Point:
    x: float
    y: float
 
@dataclass
class Circle:
    center: Point
    radius: float
 
@dataclass
class Rectangle:
    top_left: 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) if r > 100:
            return f"Large circle at ({center.x}, {center.y})"
        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(top_left=tl, width=w, height=h):
            return f"Rectangle at ({tl.x}, {tl.y}), {w}x{h}"
        case _:
            return "Unknown shape"
 
print(describe_shape(Circle(Point(0, 0), 5)))
# Circle at origin with radius 5
 
print(describe_shape(Rectangle(Point(1, 2), 10, 10)))
# Square with side 10

Für Klassen ohne __match_args__ verwende Keyword-Argumente. Dataclasses und named tuples setzen __match_args__ automatisch und ermöglichen damit Positions-Patterns:

@dataclass
class Color:
    r: int
    g: int
    b: int
 
def describe_color(color):
    match color:
        case Color(0, 0, 0):
            return "Black"
        case Color(255, 255, 255):
            return "White"
        case Color(r, 0, 0):
            return f"Red shade (r={r})"
        case Color(0, g, 0):
            return f"Green shade (g={g})"
        case Color(0, 0, b):
            return f"Blue shade (b={b})"
        case Color(r, g, b):
            return f"RGB({r}, {g}, {b})"
 
print(describe_color(Color(255, 0, 0)))   # Red shade (r=255)
print(describe_color(Color(128, 64, 32))) # RGB(128, 64, 32)

Built-in Types matchen

Du kannst gegen Built-in Types matchen, um Type Checking zu machen:

def process_value(value):
    match value:
        case int(n):
            return f"Integer: {n}"
        case float(n):
            return f"Float: {n}"
        case str(s):
            return f"String: '{s}'"
        case list(items):
            return f"List with {len(items)} items"
        case dict(d):
            return f"Dict with keys: {list(d.keys())}"
        case _:
            return f"Other type: {type(value).__name__}"
 
print(process_value(42))          # Integer: 42
print(process_value("hello"))     # String: 'hello'
print(process_value([1, 2, 3]))   # List with 3 items

Match Case vs if-elif: Wann was verwenden?

Kriteriummatch-caseif-elif
Python-Versionnur 3.10+alle Versionen
Wertvergleichsauber, ein Wert pro Caseausführlich, Variable wiederholt sich
Destrukturierungeingebaut (Sequenzen, Dicts, Objekte)manuelles Unpacking nötig
Type Checkingnative Class Patternserfordert isinstance()
Guard-Conditionsif nach dem Patternnormales if/elif
Variable Bindingautomatisches Capturemanuelle Zuweisung
Performanceoptimierter Pattern-Dispatchsequentielle Auswertung
Lesbarkeitbesser bei 4+ Fällen mit Strukturbesser bei 1–3 einfachen Bedingungen
Fall-throughnein (by design)möglich bei fehlendem elif

Wann match-case klar besser ist:

# Parsing structured data -- match-case
def handle_event(event):
    match event:
        case {"type": "click", "x": x, "y": y}:
            process_click(x, y)
        case {"type": "keypress", "key": key, "modifiers": [*mods]}:
            process_key(key, mods)
        case {"type": "scroll", "delta": delta} if delta != 0:
            process_scroll(delta)

Das Äquivalent mit if-elif:

# Same logic with if-elif -- more verbose
def handle_event(event):
    if event.get("type") == "click" and "x" in event and "y" in event:
        process_click(event["x"], event["y"])
    elif event.get("type") == "keypress" and "key" in event and "modifiers" in event:
        process_key(event["key"], event["modifiers"])
    elif event.get("type") == "scroll" and event.get("delta", 0) != 0:
        process_scroll(event["delta"])

Wann if-elif immer noch besser ist:

# Simple range checks -- if-elif is clearer
if temperature > 100:
    status = "boiling"
elif temperature > 50:
    status = "hot"
elif temperature > 20:
    status = "warm"
else:
    status = "cold"

Match-case unterstützt Range Comparisons nicht direkt in Patterns. Du müsstest Guards für jeden Fall nutzen, was den Lesbarkeitsvorteil zunichtemacht.

Praxisnahe Use Cases

Command-Line Argument Parsing

import sys
 
def main():
    match sys.argv[1:]:
        case ["help"]:
            print("Available commands: help, version, run, test")
        case ["version"]:
            print("v1.0.0")
        case ["run", filename]:
            print(f"Running {filename}")
        case ["run", filename, "--verbose"]:
            print(f"Running {filename} with verbose output")
        case ["test", *test_files] if test_files:
            print(f"Testing: {', '.join(test_files)}")
        case ["test"]:
            print("Running all tests")
        case [unknown, *_]:
            print(f"Unknown command: {unknown}")
        case []:
            print("No command provided. Use 'help' for usage.")

State Machine Implementation

from dataclasses import dataclass
from typing import Optional
 
@dataclass
class State:
    name: str
    data: Optional[dict] = None
 
def transition(state, event):
    match (state.name, event):
        case ("idle", "start"):
            return State("loading", {"progress": 0})
        case ("loading", "progress"):
            progress = state.data.get("progress", 0) + 25
            if progress >= 100:
                return State("ready", {"progress": 100})
            return State("loading", {"progress": progress})
        case ("loading", "cancel"):
            return State("idle")
        case ("ready", "process"):
            return State("processing", state.data)
        case ("processing", "complete"):
            return State("done", {"result": "success"})
        case ("processing", "error"):
            return State("error", {"message": "Processing failed"})
        case (_, "reset"):
            return State("idle")
        case (current, unknown_event):
            print(f"No transition from '{current}' on '{unknown_event}'")
            return state
 
# Usage
state = State("idle")
for event in ["start", "progress", "progress", "progress", "progress", "process", "complete"]:
    state = transition(state, event)
    print(f"Event: {event} -> State: {state.name}, Data: {state.data}")

JSON API Response Handler

def process_api_result(response):
    match response:
        case {"data": {"users": [*users]}, "meta": {"total": total}}:
            print(f"Found {total} users, received {len(users)}")
            for user in users:
                match user:
                    case {"name": name, "role": "admin"}:
                        print(f"  Admin: {name}")
                    case {"name": name, "role": role}:
                        print(f"  {role.title()}: {name}")
 
        case {"data": {"users": []}}:
            print("No users found")
 
        case {"error": {"code": code, "message": msg}} if code >= 500:
            print(f"Server error ({code}): {msg}")
            raise RuntimeError(msg)
 
        case {"error": {"code": code, "message": msg}}:
            print(f"Client error ({code}): {msg}")
 
        case _:
            print(f"Unexpected response format: {type(response)}")
 
# Example
process_api_result({
    "data": {"users": [
        {"name": "Alice", "role": "admin"},
        {"name": "Bob", "role": "editor"}
    ]},
    "meta": {"total": 2}
})

Configuration File Parser

def apply_config(settings):
    for key, value in settings.items():
        match (key, value):
            case ("database", {"host": host, "port": int(port), "name": db}):
                print(f"DB connection: {host}:{port}/{db}")
            case ("database", _):
                raise ValueError("Invalid database config: need host, port, name")
            case ("logging", {"level": level}) if level in ("DEBUG", "INFO", "WARNING", "ERROR"):
                print(f"Log level set to {level}")
            case ("logging", {"level": level}):
                raise ValueError(f"Invalid log level: {level}")
            case ("features", {"enabled": list(features)}):
                print(f"Enabled features: {', '.join(features)}")
            case ("features", {"enabled": _}):
                raise TypeError("Features must be a list")
            case (key, _):
                print(f"Unknown config key: {key}")
 
apply_config({
    "database": {"host": "localhost", "port": 5432, "name": "mydb"},
    "logging": {"level": "INFO"},
    "features": {"enabled": ["auth", "cache", "metrics"]}
})

Data Transformation Pipeline

def transform_record(record):
    """Transform raw data records into standardized format."""
    match record:
        case {"timestamp": ts, "value": float(v) | int(v), "unit": str(unit)}:
            return {"time": ts, "measurement": float(v), "unit": unit.lower()}
 
        case {"timestamp": ts, "value": str(v), "unit": str(unit)}:
            try:
                return {"time": ts, "measurement": float(v), "unit": unit.lower()}
            except ValueError:
                return {"time": ts, "measurement": None, "unit": unit.lower(), "error": f"Cannot parse: {v}"}
 
        case {"timestamp": ts, "value": v}:
            return {"time": ts, "measurement": float(v), "unit": "unknown"}
 
        case {"values": [*values]} if values:
            return [transform_record({"timestamp": None, "value": v, "unit": "batch"}) for v in values]
 
        case _:
            return {"error": f"Unrecognized format: {record}"}
 
# Examples
print(transform_record({"timestamp": "2026-01-01", "value": 42.5, "unit": "Celsius"}))
print(transform_record({"timestamp": "2026-01-01", "value": "98.6", "unit": "F"}))

Fortgeschrittene Pattern-Techniken

AS Pattern: Das gesamte Match binden

Verwende as, um den gesamten gematchten Wert zu capturen und ihn gleichzeitig zu destrukturieren:

def process_item(item):
    match item:
        case {"name": str(name), "price": float(price)} as product if price > 100:
            print(f"Premium product: {product}")
        case {"name": str(name), "price": float(price)} as product:
            print(f"Standard product: {product}")

Pattern-Typen kombinieren

Patterns lassen sich natürlich kombinieren. Du kannst Sequence-, Mapping- und Class-Patterns mischen:

@dataclass
class Order:
    items: list
    customer: dict
 
def process_order(order):
    match order:
        case Order(items=[single_item], customer={"vip": True}):
            print(f"VIP single-item order: {single_item}")
        case Order(items=[_, _, *rest], customer={"name": name}) if len(rest) > 0:
            print(f"{name} ordered {2 + len(rest)} items")
        case Order(items=[], customer=_):
            print("Empty order")

Match-Case in Jupyter Notebooks verwenden

Pattern Matching ist besonders nützlich in Data-Analysis-Workflows, in denen du unterschiedliche Datenformate oder API-Responses behandeln musst. Beim interaktiven Explorieren von Datasets in Jupyter notebooks bietet match-case eine saubere Möglichkeit, die Vielfalt an Datenformen zu handhaben, die dir begegnet.

def classify_cell_output(output):
    """Classify Jupyter cell output by type."""
    match output:
        case {"output_type": "stream", "text": text}:
            return f"Text output: {len(text)} chars"
        case {"output_type": "error", "ename": name, "evalue": value}:
            return f"Error: {name}: {value}"
        case {"output_type": "display_data", "data": {"image/png": _}}:
            return "Image output"
        case {"output_type": "execute_result", "data": {"text/html": html}}:
            return "HTML table output"
        case _:
            return "Unknown output type"

Für Data Scientists, die KI-Unterstützung beim Arbeiten mit Pattern Matching und anderen Python-3.10+-Features in notebooks möchten, bietet RunCell (opens in a new tab) einen AI agent direkt in Jupyter, der beim Schreiben, Debuggen und Optimieren von match-case-Statements basierend auf deinen tatsächlichen Datenstrukturen helfen kann.

Häufige Fehler und wie du sie vermeidest

Fehler 1: match-case in Python < 3.10 verwenden

# This fails with SyntaxError on Python 3.9 and earlier
match value:
    case 1:
        print("one")

Fix: Prüfe sys.version_info >= (3, 10) oder upgrade Python.

Fehler 2: Case-Namen als Vergleiche behandeln

expected = 200
 
match status_code:
    case expected:  # WRONG: creates a new variable 'expected'
        print("OK")

Fix: Verwende einen dotted name, ein Literal oder einen Guard:

match status_code:
    case code if code == expected:
        print("OK")

Fehler 3: Den Wildcard-Case vergessen

Ohne case _ passiert bei nicht gematchten Werten stillschweigend nichts:

match command:
    case "start":
        run()
    case "stop":
        halt()
    # If command is "pause", nothing happens -- no error, no warning

Fix: Nimm immer einen Wildcard-Case auf, um explizit zu behandeln:

match command:
    case "start":
        run()
    case "stop":
        halt()
    case _:
        raise ValueError(f"Unknown command: {command}")

Fehler 4: Reihenfolge-abhängige Patterns ohne Spezifität

Patterns werden von oben nach unten geprüft. Ein breites Pattern vor einem spezifischen überschattet dieses:

# WRONG: the first case always matches
match point:
    case (x, y):       # Captures everything
        print("generic")
    case (0, 0):       # Never reached
        print("origin")

Fix: Setze spezifische Patterns zuerst:

match point:
    case (0, 0):
        print("origin")
    case (x, y):
        print(f"point at ({x}, {y})")

Performance-Überlegungen

Match-case wird zu effizientem Bytecode kompiliert. Für einfaches Literal Matching optimiert der Python-Compiler es ähnlich wie Dictionary Lookups. Für strukturelle Patterns erzeugt er einen Decision Tree, der redundante Checks vermeidet.

Benchmarks zeigen, dass match-case für kleine Fallzahlen (unter 5) vergleichbar zu if-elif-Ketten performt. Für größere Dispatch-Tabellen (10+ Fälle) kann match-case durch Bytecode-Optimierung schneller sein, besonders mit Literal Patterns.

Allerdings ist Pattern Matching kein Ersatz für Dictionary Dispatch, wenn du lediglich Werte auf Funktionen abbilden willst:

# For simple value-to-action mapping, a dict is cleaner and faster
handlers = {
    "click": handle_click,
    "scroll": handle_scroll,
    "keypress": handle_keypress,
}
 
handler = handlers.get(event_type, handle_unknown)
handler(event_data)

Nutze match-case, wenn du strukturelle Inspektion, Destrukturierung oder Guard-Conditions brauchst – Dinge, die ein Dictionary Lookup nicht ausdrücken kann.

FAQ

Welche Python-Version brauche ich für match-case?

Python 3.10 oder neuer ist für das match-case-Statement erforderlich. Es wurde in PEP 634 als Teil des Python-3.10-Releases im Oktober 2021 eingeführt. Wenn du match-case in Python 3.9 oder älter verwendest, bekommst du eine SyntaxError. Du kannst deine Version prüfen, indem du python --version im Terminal ausführst.

Ist Python match-case dasselbe wie switch-case in anderen Sprachen?

Nein. Pythons match-case ist strukturelles Pattern Matching und deutlich mächtiger als ein klassisches switch-case. Während switch-case in C oder Java nur Werte vergleicht, kann Pythons match-case Sequenzen und Dictionaries destrukturieren, Klasseninstanzen anhand ihrer Attribute matchen, Variablen aus gematchten Daten binden und Guard-Conditions anwenden. Es ist näher an Pattern Matching in Rust, Scala oder Haskell.

Hat match-case Fall-through wie C switch?

Nein. Pythons match-case führt nur den ersten passenden Case-Block aus und verlässt dann das match-Statement. Es gibt kein Fall-through-Verhalten und keine Notwendigkeit für break-Statements. Wenn mehrere Patterns denselben Code ausführen sollen, nutze Or-Patterns mit dem Pipe-Operator: case "a" | "b" | "c": matcht jeden dieser Werte.

Kann ich match-case mit regulären Ausdrücken verwenden?

Nicht direkt. Patterns in match-case sind strukturell, nicht regex-basiert. Du kannst aber Guard Clauses verwenden, um Regex-Matching anzuwenden: case str(s) if re.match(r"pattern", s): kombiniert strukturelles Type Checking mit Regex-Validierung im Guard.

Wie geht match-case mit None-Werten um?

None wird als Literal Pattern mit case None: gematcht. Da None ein Singleton in Python ist, verwendet das Matching Identitätsvergleich (entspricht is None). Das bedeutet, case None: matcht nur das echte None-Objekt, nicht andere falsy Werte wie 0, False oder leere Strings.

Was passiert, wenn kein Case matcht?

Wenn kein Case matcht und es kein Wildcard-Pattern (case _:) gibt, tut das match-Statement stillschweigend nichts – die Ausführung geht nach dem Match-Block weiter. Es wird keine Exception geworfen. Deshalb ist es Best Practice, immer einen Wildcard-Case einzubauen, entweder für ein Default-Verhalten oder um bei unerwarteten Werten explizit eine Exception zu werfen.

Fazit

Pythons match-case-Statement bringt strukturelles Pattern Matching in eine Sprache, die lange auf if-elif-Ketten für komplexe Verzweigungslogik angewiesen war. Es bietet einen deklarativen Weg, Datenformen zu inspizieren, Sequenzen und Dictionaries zu destrukturieren, nach Objekttypen zu dispatchen und Variablen zu binden – alles in einer Syntax, die klarer lesbar ist als der entsprechende bedingte Code.

Die zentrale Erkenntnis: match-case ist nicht einfach nur ein switch-Statement. Es ist ein Werkzeug für strukturierte Daten: Commands parsen, API-Responses behandeln, State Machines implementieren und Routing basierend auf Objekttypen. Wenn deine Branching-Logik eher die Form der Daten prüft als simple Wertvergleiche, erzeugt match-case Code, der sowohl kürzer als auch leichter zu warten ist.

Starte mit einfachen Literal Patterns und integriere dann schrittweise Sequence Destructuring, Mapping Patterns und Class Patterns, sobald deine Use Cases es verlangen. Baue immer einen Wildcard-Case für die explizite Behandlung unerwarteter Inputs ein, setze spezifische Patterns vor allgemeine und denke daran, dass „nackte“ Namen Werte capturen, statt gegen sie zu vergleichen.

Für interaktives Explorieren von match-case-Patterns mit deinen eigenen Daten bieten sich Jupyter notebooks als ideale Umgebung an, um Patterns inkrementell zu testen. RunCell (opens in a new tab) erweitert diesen Workflow mit KI-Unterstützung, die Python-3.10+-Features versteht und Pattern-Strukturen basierend auf deinen Datenformen vorschlagen kann.

📚