How does structural pattern matching (match/case) work in Python?
Structural pattern matching (PEP 634, Python 3.10+) provides a powerful way to match values against patterns, similar to switch/case in other languages but much more powerful.
Patterns can match:
- Literal values: Numbers, strings, booleans, None
- Capture patterns: Bind matched values to variables
- Sequence patterns: Match lists/tuples by structure
- Mapping patterns: Match dicts by keys
- Class patterns: Match objects by type and attributes
- OR patterns: Combine patterns with
| - Guard clauses: Add conditions with
if - Wildcard:
_matches anything
match subject:
case pattern1:
action1
case pattern2 if guard:
action2
case _:
default action
Patterns: literals, captures, sequences, mappings, classes, |, guards
# Literal matching (like switch/case)
def http_status(status):
match status:
case 200:
return "OK"
case 301 | 302:
return "Redirect"
case 404:
return "Not Found"
case 500 | 502 | 503:
return "Server Error"
case _:
return f"Unknown: {status}"
print(http_status(200)) # OK
print(http_status(302)) # Redirect
# Sequence pattern matching
def process_command(command):
match command.split():
case ["quit"]:
return "Goodbye!"
case ["go", direction]:
return f"Moving {direction}"
case ["pick", "up", item]:
return f"Picked up {item}"
case ["attack", target, "with", weapon]:
return f"Attacking {target} with {weapon}"
case [action, *args]:
return f"Unknown action: {action}, args: {args}"
print(process_command("go north")) # Moving north
print(process_command("pick up sword")) # Picked up sword
print(process_command("attack dragon with fire")) # Attacking dragon with fire
# Mapping pattern (dict matching)
def process_event(event):
match event:
case {"type": "click", "x": x, "y": y}:
return f"Click at ({x}, {y})"
case {"type": "keypress", "key": key}:
return f"Key pressed: {key}"
case {"type": "scroll", "direction": "up" | "down" as direction}:
return f"Scrolling {direction}"
case {"type": unknown_type}:
return f"Unknown event: {unknown_type}"
print(process_event({"type": "click", "x": 100, "y": 200}))
# Click at (100, 200)
# Class pattern matching
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Circle:
center: Point
radius: float
@dataclass
class Rectangle:
top_left: Point
bottom_right: Point
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} with radius {r}"
case Circle(center=center, radius=r):
return f"Circle at {center} with radius {r}"
case Rectangle(top_left=Point(x=x1, y=y1), bottom_right=Point(x=x2, y=y2)):
return f"Rectangle from ({x1},{y1}) to ({x2},{y2})"
case _:
return "Unknown shape"
print(describe_shape(Circle(Point(0, 0), 5))) # Circle at origin with radius 5
print(describe_shape(Circle(Point(1, 2), 200))) # Large circle...
# Guard clauses
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(0)) # zero
print(classify_number(4)) # positive even
- •match/case supports literals, sequences, mappings, classes, and guards
- •| combines patterns (OR); _ is the wildcard (default case)
- •Capture variables bind matched values: case [x, y] binds x and y
- •Class patterns match by type and attributes: case Circle(radius=r)
- •Guards (if condition) add extra checks to patterns
- •match/case is NOT the same as switch/case; patterns are structural, not just equality
- •Variable names in patterns are captures, not comparisons; use literals or dotted names for comparison
- •case x: captures any value into x; to compare to a variable, use a guard: case _ if value == x