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 5

The 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 if to 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

Example · python
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: 42

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

Example · python
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 degrees

Class pattern matching

Matches dataclass instances by their type and extracts named attributes in the case clause.

Example · python
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: Enter

OR patterns and guards

Combines OR patterns to match multiple values and a guard clause to refine the match condition.

Example · python
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

  • Be the first to comment on this lesson.