Python 3.10 introduced the match/case statement, a powerful form of pattern matching that goes well beyond a C-style switch. It lets you match not only against simple values but also against the shape of data: tuples, lists, dicts, class instances, even combinations of them. For code that dispatches on the structure of a value, match is the clearest tool Python now offers.
The simplest cases look like a switch: match status: case 200: ...; case 404: .... The wildcard is a bare _ which matches anything and binds no variable. Already with that you have replaced a chain of if/elif branches with something easier to read.
The real power comes from structural patterns. case [first, *rest]: matches any non-empty list and binds the first item and the tail; case {"kind": "login", "user": name}: matches a dict with those keys and binds name. Class patterns like case Point(x=0, y=y): match an instance of Point whose x is 0 and bind the y coordinate. Combine them with | for alternatives and with if guards for extra conditions.
match is not a drop-in replacement for every if/elif chain — for simple boolean dispatch, if is still clearer. Reach for match when you are branching on the shape of complex data (parse trees, message dispatch, nested configurations). Used well, it removes a lot of error-prone indexing and isinstance checks.
Value and wildcard patterns
case 200: matches the literal value. case (200 | 201 | 204): uses the or-pattern. case _: is the catch-all. Unlike an if/elif chain, the first matching case runs; order matters.
Capture names bind: case code: matches anything and binds it to code. That makes match both a dispatch and a destructuring tool in one.
Structural and class patterns
Sequence patterns: case [], case [x], case [x, y], case [x, *rest]. Mapping patterns: case {"key": v} matches dicts containing at least that key. Class patterns: case Point(x=0, y=y) matches by type and optional attribute values.
Add if condition after the pattern for extra constraints: case Point(x=x, y=y) if x == y:. Use | to alternate between patterns that produce the same binding.
match/case syntax overview.
| Tool | Purpose |
|---|---|
match expr: case ...statement | Structural pattern matching. |
PEP 636tutorial | Official pattern matching tutorial. |
case Class(attr=val)pattern | Match by isinstance + attribute values. |
case {'k': v}pattern | Match a dict and bind a value. |
case [a, *rest]pattern | Match a sequence with capture. |
case a | bpattern | Match either alternative. |
case ... if condpattern | Add a boolean guard to any case. |
case _pattern | Catch-all wildcard (no binding). |
Working with Pattern Matching code example
The script dispatches on a small protocol of messages using match/case for each message shape.
# Lesson: Working with Pattern Matching
from dataclasses import dataclass
@dataclass
class Move:
dx: int
dy: int
@dataclass
class Shoot:
angle: float
def handle(message):
match message:
case {"kind": "login", "user": name}:
return f"login: {name}"
case {"kind": "logout"}:
return "logout"
case Move(dx=0, dy=0):
return "idle"
case Move(dx=dx, dy=dy) if abs(dx) + abs(dy) == 1:
return f"step {dx:+d}/{dy:+d}"
case Move(dx=dx, dy=dy):
return f"jump ({dx}, {dy})"
case Shoot(angle=a) if a < 0:
return "invalid angle"
case Shoot(angle=a):
return f"shoot @ {a:.0f} deg"
case [first, *rest]:
return f"batch: first={first} rest={rest}"
case _:
return f"unknown: {message!r}"
messages = [
{"kind": "login", "user": "ana"},
{"kind": "logout"},
Move(0, 0),
Move(1, 0),
Move(3, 2),
Shoot(-5),
Shoot(90),
[1, 2, 3],
42,
]
for m in messages:
print(f"{type(m).__name__:15s} -> {handle(m)}")
Watch how each case narrows the pattern:
1) Dict patterns match on keys and bind captured values.
2) Class patterns match on type and optionally on attributes.
3) Guards with `if` turn loose matches into precise conditions.
4) The wildcard `_` at the end is a catch-all for unmatched shapes.
Dispatch on a list structure.
def classify(expr):
match expr:
case ["+", a, b]:
return a + b
case ["*", a, b]:
return a * b
case ["neg", a]:
return -a
case _:
raise ValueError(f"unknown expression: {expr!r}")
print(classify(["+", 3, 4]))
print(classify(["*", 2, 5]))
print(classify(["neg", 7]))
Confirm each pattern kind.
def f(x):
match x:
case 0: return "zero"
case int(): return "int"
case [a, b]: return f"pair {a},{b}"
case _: return "other"
assert f(0) == "zero"
assert f(5) == "int"
assert f([1, 2]) == "pair 1,2"
assert f("x") == "other"
Running prints:
dict -> login: ana
dict -> logout
Move -> idle
Move -> step +1/+0
Move -> jump (3, 2)
Shoot -> invalid angle
Shoot -> shoot @ 90 deg
list -> batch: first=1 rest=[2, 3]
int -> unknown: 42