Structural Pattern Matching (PEP 634)
Master Python structural pattern matching: AST grammar, pattern destructuring engines, guard conditions, __match_args__ class patterns, and MATCH bytecode instructions.
Introduced in Python 3.10 via PEP 634, Structural Pattern Matching provides declarative destructuring and pattern resolution over arbitrary data shapes.
Unlike traditional switch statements in C or Java that evaluate simple scalar equality, Python's match/case construct inspects object structure, validates sequence lengths, confirms mapping keys, extracts class attributes, and binds matched components to local variables in a single expressive statement.
Introduced dedicated pattern matching bytecodes in Python 3.10 for high-performance structural destructuring.
1. Formal Mathematical Pattern Semantics#
Let represent the target evaluation object, and represent a composite structural pattern. Pattern evaluation is defined as a boolean match predicate combined with a substitution environment :
If an optional guard expression if G is attached to a case branch, the overall match succeeds if and only if and .
2. Pattern Matching Syntax & Types#
Inspect the diverse pattern forms supported by the Python pattern matching engine:
12345678910111213141516171819202122232425262728from dataclasses import dataclass @dataclass class Command: action: str target: str def process_event(event: object) -> str: match event: # Literal pattern case "QUIT": return "Terminating application" # Sequence pattern with wildcard capture case ["MOVE", x, y] if isinstance(x, (int, float)) and isinstance(y, (int, float)): return f"Moving to coordinate ({x}, {y})" # Mapping pattern (sub-dict matching) case {"type": "NOTIFICATION", "payload": message}: return f"Received notification: {message}" # Class pattern with attribute extraction case Command(action="DELETE", target=file_path): return f"Deleting file at {file_path}" # Catch-all wildcard case _: return "Unrecognized event format"
3. Class Patterns & The __match_args__ Protocol#
When matching against custom class instances, how does Python know which positional arguments map to which instance attributes?
Python relies on the __match_args__ class attribute: a tuple of attribute name strings. Dataclasses and namedtuples generate __match_args__ automatically:
12345678910111213141516171819class Point2D: # Explicitly define positional attribute mapping order __match_args__ = ("x", "y") def __init__(self, x: float, y: float): self.x = x self.y = y def inspect_point(p: object): match p: # Matches if isinstance(p, Point2D) and binds p.x -> a, p.y -> b case Point2D(0, 0): print("At the origin") case Point2D(a, 0): print(f"On X axis at x={a}") case Point2D(0, b): print(f"On Y axis at y={b}") case Point2D(a, b): print(f"At coordinate ({a}, {b})")
4. Dedicated Pattern Matching Bytecodes#
Under the hood, CPython uses dedicated bytecode opcodes to evaluate patterns with maximum efficiency:
| Opcode | Operational Behavior |
|---|---|
MATCH_SEQUENCE | Checks whether the target object implements the sequence protocol and is not an atomic string or byte buffer. |
MATCH_MAPPING | Checks whether the target object implements the mapping protocol (PyDict_Check or PyMapping_Check). |
MATCH_KEYS | Checks whether keys specified in a mapping pattern exist in the target dictionary and pushes matching values. |
MATCH_CLASS | Validates isinstance() against the target class and extracts positional attributes mapped via __match_args__. |
str and bytes implement the sequence protocol. However, inside match/case blocks, MATCH_SEQUENCE explicitly excludes strings and byte buffers! This prevents strings from inadvertently matching sequence patterns like [first, *rest].5. Concept Verification#
Test your understanding of structural pattern matching:
How does Python determine which class attributes correspond to positional parameters in class patterns (e.g. case Point(x, y):)?
Why does the MATCH_SEQUENCE opcode explicitly exclude str and bytes instances from matching sequence patterns?