Structural pattern matching, introduced in Python 3.10, is the match-case statement. It lets you compare a value against patterns that describe its shape, not just its equality to constants. You can destructure sequences, match dictionary keys, check class types while binding attributes, and add conditional guards.
The match statement replaces deeply nested if-elif chains that check types, lengths, and key membership. Where you used to write isinstance calls followed by indexing and attribute access, you now write a pattern that declares the structure you expect.
Pattern matching is especially useful for processing JSON-like data, handling AST nodes in compilers, implementing parsers, and building command dispatchers. It makes code that deals with structured data dramatically more readable.
The article on advanced Python mistakes to avoid covers other cases where a simpler, more declarative tool beats a chain of manual checks.
Matching simple values and sequences
The simplest patterns match against literal values. More useful patterns destructure sequences, binding elements to names as part of the match.
def describe(command):
match command:
case ["help"]:
return "Showing help"
case ["greet", name]:
return f"Hello, {name}"
case ["add", a, b]:
return int(a) + int(b)
case _:
return "Unknown command"
print(describe(["help"]))
print(describe(["greet", "Maya"]))
print(describe(["add", "3", "4"]))Each case describes a specific list shape. The elements are bound to names automatically. The underscore is the wildcard that matches anything.
Showing help
Hello, Maya
7The star pattern captures all remaining elements beyond the first into a new list. This approach handles variable-length sequences gracefully without requiring an exact count of elements to be specified.
def process(items):
match items:
case [first, *rest]:
print(f"First: {first}, Rest: {rest}")
process([1, 2, 3, 4])The first element is bound to the name first as a single value. All remaining elements are collected into a newly created list bound to the name rest for further processing.
First: 1, Rest: [2, 3, 4]Matching mappings and dictionaries
Mapping patterns match dictionaries by their keys. You specify the keys you care about and bind their values to names. Extra keys are ignored by default.
def handle_event(event):
match event:
case {"type": "click", "x": x, "y": y}:
return f"Clicked at ({x}, {y})"
case {"type": "keypress", "key": key}:
return f"Pressed {key}"
case _:
return "Unknown event"
print(handle_event({"type": "click", "x": 100, "y": 200}))
print(handle_event({"type": "keypress", "key": "Enter"}))Only the specified keys are matched. Any additional keys in the dictionary are ignored. Missing required keys cause the pattern to fail.
Clicked at (100, 200)
Pressed EnterMatching class instances
Class patterns check the type of a value and bind its attributes in a single step. This replaces the pattern of calling isinstance followed by manual attribute access.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
class Circle:
def __init__(self, center, radius):
self.center = center
self.radius = radius
def area(shape):
match shape:
case Circle(center=Point(x=x, y=y), radius=r):
return 3.14 * r * r
case _:
return 0
c = Circle(Point(10, 20), 5)
print(area(c))The pattern matches nested class structures and binds deeply nested attributes to names. The entire isinstance check, attribute access, and binding happens declaratively.
78.5The class pattern uses keyword arguments to match attributes. If the class uses match_args, positional matching is also supported for simpler cases.
Adding guards for conditional matching
A guard is an if clause after a pattern. The pattern must match and the guard must evaluate to True for the case to execute. This adds conditional logic without nesting.
def categorize(number):
match number:
case n if n < 0:
return "negative"
case n if n == 0:
return "zero"
case n if n % 2 == 0:
return "even"
case _:
return "odd"
print(categorize(-5))
print(categorize(0))
print(categorize(4))
print(categorize(7))Each case matches any number but the guard restricts which numbers actually reach the body. The wildcard catches anything not filtered by earlier guards.
negative
zero
even
oddOR patterns for combining alternatives
The pipe operator combines multiple patterns into a single case. If any of the alternatives match, the case executes. This eliminates duplicate case bodies.
def is_special(code):
match code:
case 200 | 201 | 204:
return True
case 400 | 404 | 405:
return False
case _:
return None
print(is_special(200))
print(is_special(404))
print(is_special(500))Three status codes share the same handling. The OR pattern keeps the logic in one place instead of copying it across multiple case blocks.
True
False
NoneThe OR pattern works with all pattern types, including sequence patterns, mapping patterns, and class patterns. Each alternative is tried in order until one matches or all fail.
The article on special methods in Python covers the dunder methods, like __eq__ and __repr__, that classes used in patterns typically also define.
Rune AI
Key Insights
- match-case destructures data by shape, not just by value like a switch statement.
- Sequence patterns match lists and tuples; use *rest to capture remaining elements.
- Mapping patterns match dicts by key; use **rest to capture remaining entries.
- Class patterns match by type and bind attributes in one step.
- Guard clauses (if after a pattern) add conditional logic without nesting.
Frequently Asked Questions
Is Python's match-case the same as a switch statement?
When should I use match-case instead of if-elif?
Conclusion
Structural pattern matching is one of the most significant Python syntax additions in years. It turns nested isinstance checks and dictionary key lookups into readable, declarative patterns. Start with simple value matching and sequence destructuring. Add class patterns when you process typed data. Use guards when logic goes beyond structural shape. The match statement rewards you with code that reads like a description of the data you expect.
More in this topic
How to Test Python Code
Learn what testing means in Python, the core ideas behind automated tests, and the built-in and third-party tools that make testing practical.
Write Your First Python Unit Test
Write and run your first Python unit test step by step, using unittest to check a small function and understand the test output.
The Factory Pattern in Python
Learn how to use the factory pattern in Python to encapsulate object creation, making your code more flexible and easier to extend.