Structural Pattern Matching with match/case
Python 3.10 introduced a powerful new feature: structural pattern matching, commonly known as the match/case statement. If you’ve used switch statements in other languages, you might think you already know what this is. But Python’s implementation goes far beyond simple value switching—it lets you match against data structures, extract values, and handle complex conditional logic with clean, readable syntax.
Why Use match/case?
Before the match statement, you’d handle complex conditional logic with a chain of if/elif/else statements or a dictionary dispatch pattern. Both work, but they become unwieldy as conditions grow. The match statement lets you express intent directly: “match this value against these patterns.”
Consider a function that processes HTTP status codes:
def handle_response(status):
match status:
case 400:
return "Bad request"
case 401:
return "Unauthorized"
case 403:
return "Forbidden"
case 404:
return "Not found"
case 500:
return "Internal server error"
case _:
return f"Unknown status code: {status}"
The _ wildcard pattern matches anything—it’s like the final else in an if chain.
Literal Patterns
The simplest form of matching uses literal values. Strings, numbers, and booleans all work as patterns:
def describe_number(n):
match n:
case 0:
return "Zero"
case 1:
return "One"
case 2:
return "Two"
case _:
return f"Something else: {n}"
You can also match multiple literals with the | operator (the “or pattern”):
def http_method(method):
match method.upper():
case "GET" | "HEAD" | "OPTIONS":
return "Safe method"
case "POST" | "PUT" | "PATCH":
return "Unsafe method"
case _:
return "Unknown method"
Capturing Patterns
Sometimes you want to capture the matched value for use in the case body. Prefix a pattern with a variable name to capture it:
def process(data):
match data:
case [x, y]:
return f"Got a two-element list: {x}, {y}"
case {"name": name, "age": age}:
return f"Got a dict with name={name}, age={age}"
case _:
return "Unknown structure"
The variable name (without quotes) captures whatever matches that position.
Class Patterns
One of the most powerful features is matching against class structures. You can match dataclasses, namedtuples, and regular classes with positional or keyword arguments:
from dataclasses import dataclass
from datetime import datetime
@dataclass
class Event:
timestamp: datetime
source: str
@dataclass
class ClickEvent(Event):
element_id: str
@dataclass
class PageViewEvent(Event):
path: str
duration_seconds: int
def process_event(event):
match event:
case ClickEvent(timestamp, _, element_id):
return f"Click on {element_id} at {timestamp}"
case PageViewEvent(timestamp, _, path, duration):
return f"Viewed {path} for {duration}s"
case _:
return "Unknown event type"
Note how we use _ to ignore values we don’t need—in this case, the source field.
Pattern Guards
Sometimes you need an extra condition beyond the pattern itself. That’s what guards are for—if clauses after your pattern:
def classify_point(point):
match point:
case (0, 0):
return "Origin"
case (x, 0) if x > 0:
return f"Positive x-axis: {x}"
case (0, y) if y > 0:
return f"Positive y-axis: {y}"
case (x, y):
return f"Point at ({x}, {y})"
The guard (if x > 0) is only checked if the pattern matches.
Matching with AS
The as pattern lets you both match a pattern and capture the entire match:
def handle_response(response):
match response:
case {"status": 200, "data": data} as success:
return f"Success with data: {data}"
case {"error": error_msg} as error:
return f"Error: {error_msg}"
case _:
return "Unexpected response format"
When to Use match/case
The match statement shines when you’re:
- Processing structured data (JSON, dataclasses, namedtuples)
- Implementing state machines
- Handling protocol messages or command parsing
- Replacing complex
if/elifchains that compare against multiple values
For simple value comparisons, a dictionary dispatch or simple if/else might still be clearer. The match statement earns its keep when patterns become complex.
See Also
python-dataclasses— Create structured data classes that work great with pattern matchingerror-handling— Python’s exception handling for robust code