Python Match Case: Structural Pattern Matching Guide
Structural pattern matching, introduced in Python 3.10 via PEP 634,
provides a powerful and expressive mechanism to evaluate expressions
against specific patterns, extract data, and execute conditional logic
based on an object's structure. This article explains how the
match and case statements operate, exploring
basic literal matching, sequence and mapping destructuring, class
instance extraction, and pattern guards.
The Basic Syntax
At its core, structural pattern matching compares a subject
expression against one or more patterns defined in case
blocks. Unlike standard switch/case statements
found in languages like C or JavaScript, Python's implementation matches
both values and data shapes.
def process_command(command: str) -> None:
match command:
case "start":
print("System starting...")
case "stop":
print("System stopping...")
case _:
print(f"Unknown command: {command}")The underscore _ acts as a wildcard pattern that matches
any value, functioning as the default fallback branch. Unlike some
languages, execution does not "fall through" from one case to the next;
once a pattern succeeds, its block executes, and matching
terminates.
Sequence Destructuring
Structural pattern matching shines when unpacking sequences such as lists and tuples. You can check both the length of a sequence and bind its individual elements to variables.
def process_coordinates(point: tuple | list) -> None:
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"On the Y-axis at {y}")
case (x, 0):
print(f"On the X-axis at {x}")
case (x, y):
print(f"Point at ({x}, {y})")
case (x, y, *rest):
print(f"3D+ Point starting at ({x}, {y}) with extra dimensions: {rest}")
case _:
print("Not a valid coordinate")Variable names inside a pattern act as capture targets. If the structure matches, Python assigns the corresponding parts of the subject to those variables.
Mapping Destructuring
Patterns can match dictionary keys and bind their values. A dictionary pattern matches even if the dictionary contains additional keys not specified in the case, checking only for the presence of the specified keys.
def handle_event(event: dict) -> None:
match event:
case {"type": "click", "x": x, "y": y}:
print(f"Click at position ({x}, {y})")
case {"type": "keypress", "key": key}:
print(f"Key pressed: {key}")
case _:
print("Unrecognized event structure")To capture extra key-value pairs, the **rest syntax can
be used.
Class and Object Matching
You can match against class instances by specifying the class name followed by positional or keyword patterns. This inspects object attributes directly.
class Point:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def inspect_point(obj: object) -> None:
match obj:
case Point(x=0, y=0):
print("Point is at the origin")
case Point(x=x, y=y):
print(f"Point instance at ({x}, {y})")
case _:
print("Not a Point instance")If a class defines __match_args__, positional arguments
can be used directly within the pattern without naming the attributes
explicitly.
Pattern Guards and Combined Patterns
Patterns can be combined using the | (OR) operator, and
they can include conditional expressions called "guards" using an
if clause.
def validate_response(status: int, payload: dict) -> None:
match status, payload:
case (200 | 201, {"data": data}):
print(f"Success with data: {data}")
case (400 | 404 | 500, {"error": error}) if len(error) > 0:
print(f"Failed with detailed error: {error}")
case (status_code, _):
print(f"Unhandled status: {status_code}")A guard evaluates only after the structural pattern matches. If the
guard evaluates to False, matching continues to the next
case.
Key Differences from
if/elif/else
Structural pattern matching is not a direct replacement for simple
conditional chains. Use match/case when:
- Destructuring is required: When you need to unpack values from sequences, dictionaries, or objects while verifying their types and shapes.
- Handling polymorphic data: When managing varying input formats like parsed JSON payloads or abstract syntax trees.
- Readability is improved: When deeply nested
isinstance()checks and dictionary lookups clutter the control flow.