Structural Pattern Matching
match/case destructures data by shape — tuples, lists, dicts, classes and guards, not just values.
You met match as a tidy replacement for if/elif. Its real power is structural: each case can describe the shape of the data and pull values out of it in the same step.
Patterns that capture
point = (0, 5)
match point:
case (0, 0):
print('origin')
case (0, y): # matches (0, anything), binds y
print(f'on Y axis at {y}')
case (x, 0):
print(f'on X axis at {x}')
case (x, y):
print(f'at {x}, {y}')
# -> on Y axis at 5The pattern kinds
- Sequence:
[a, b, *rest]matches lists/tuples and captures the tail. - Mapping:
{'type': 'user', 'name': n}matches dicts with those keys. - Class:
Point(x=0, y=y)matches instances and reads attributes. - Guard: add
ifto a case for an extra condition. - Or:
case 'y' | 'yes' | 'true':matches any alternative.
Beware one trap: a bare name like case x: is a capture that matches anything, not a comparison to a variable x. To match against a constant, use a dotted name (case Color.RED:) or a literal.
Example
def handle(event):
match event:
# mapping pattern with capture
case {'type': 'click', 'x': x, 'y': y}:
return f'click at ({x}, {y})'
# sequence pattern capturing a tail
case ['move', *steps] if steps:
return f'move through {len(steps)} points'
# mapping pattern with a guard
case {'type': 'key', 'code': code} if code < 32:
return f'control key {code}'
case {'type': 'key', 'code': code}:
return f'key {chr(code)}'
# or-pattern
case 'quit' | 'exit' | 'q':
return 'shutting down'
# catch-all with capture
case other:
return f'unhandled: {other!r}'
tests = [
{'type': 'click', 'x': 10, 'y': 20},
['move', (0, 0), (1, 1), (2, 2)],
{'type': 'key', 'code': 13},
{'type': 'key', 'code': 65},
'exit',
42,
]
for t in tests:
print(handle(t))
# Output:
# click at (10, 20)
# move through 3 points
# control key 13
# key A
# shutting down
# unhandled: 42When to use it
- An event dispatcher uses structural pattern matching to destructure incoming event dicts by their 'type' key.
- A CLI parser matches command tuples like ('move', x, y) to dispatch to the correct handler function.
- A JSON schema validator uses class patterns to confirm that parsed data matches expected dataclass shapes.
More examples
Destructure a tuple with match
Matches tuple shapes and binds their elements to variables in one compact case clause.
def handle(cmd):
match cmd:
case ('move', x, y):
print(f'Move to ({x}, {y})')
case ('fire', angle):
print(f'Fire at {angle} degrees')
case _:
print('Unknown command')
handle(('move', 10, 20)) # Move to (10, 20)
handle(('fire', 45)) # Fire at 45 degreesClass pattern matching
Matches dataclass instances by their type and extracts named attributes in the case clause.
from dataclasses import dataclass
@dataclass
class Click: x: int; y: int
@dataclass
class Keypress: key: str
def dispatch(event):
match event:
case Click(x=x, y=y): print(f'Click at {x},{y}')
case Keypress(key=k): print(f'Key: {k}')
dispatch(Click(100, 200)) # Click at 100,200
dispatch(Keypress('Enter')) # Key: EnterOR patterns and guards
Combines OR patterns to match multiple values and a guard clause to refine the match condition.
def classify(value):
match value:
case 0 | 0.0:
return 'zero'
case int(n) | float(n) if n < 0:
return 'negative'
case int() | float():
return 'positive'
case str():
return 'string'
print(classify(-3)) # negative
print(classify(0.0)) # zero
print(classify('hi')) # string
Discussion