Python match-case
Satellite · Control Flow
Structural Pattern Matching
`match-case` checks the shape of your data and destructures it in the same step. Handle payloads and state machines with clarity.
Syntax
import sys
match command:
case "quit":
sys.exit()
case "hello":
print("Hello!")
case _:
print(f"Unknown command: {command}")
_is a wildcard that matches anything.- Requires Python 3.10+.
Used like this, match is just a switch — and if/elif would do equally well. Its real value shows when patterns describe structure.
Matching data structures
Each case can check the type, the keys or positions, and bind the parts you need — all in one line:
match event:
case {"type": "user.created", "payload": payload}:
handle_user(payload)
case {"type": "invoice.failed", "invoice_id": inv_id}:
alert_finance(inv_id)
case [first, *rest]:
process(first, rest)
- Dict patterns match the listed keys and ignore extra ones — exactly right for API payloads that grow fields over time.
- Sequence patterns destructure like unpacking;
*restcaptures the remainder. - The equivalent
ifversion needsisinstancechecks plus.get()calls plus manual unpacking for every branch.
Class patterns
Patterns match object attributes too — cleanest with dataclasses:
from dataclasses import dataclass
@dataclass
class Click:
x: int
y: int
button: str = "left"
match event:
case Click(x=0, y=0):
print("origin clicked")
case Click(x=x, y=y, button="right"):
open_context_menu(x, y)
Guards
Guards add a Boolean condition to a structural match:
match payload:
case {"type": "user", "active": True} if payload["tier"] == "pro":
onboard_pro_user(payload)
OR-patterns
Alternatives share one body with |:
match answer:
case "y" | "yes" | "ok":
proceed()
case "n" | "no":
abort()
The capture gotcha
A bare name in a pattern captures — it does not compare:
RED = "red"
match color:
case RED: # matches ANYTHING and rebinds RED to it
stop()
case "blue": # unreachable — Python raises SyntaxError:
go() # "name capture 'RED' makes remaining patterns unreachable"
To compare against a constant, use a dotted name:
class Color:
RED = "red"
BLUE = "blue"
match color:
case Color.RED: # dotted = look up and compare
stop()
case Color.BLUE:
go()
match vs if/elif
- Use
matchwhen the shape of the data decides what happens: parsers, event handlers, command dispatch, recursive tree walks. - Stick with
if/eliffor a few literal comparisons, conditions on different variables per branch, or code that must run on Python < 3.10. - Order matters; the first matching case wins. Keep cases simple and extract handlers for complex logic.
Frequently Asked Questions
Does match fall through like a C switch?
No. Exactly one case body runs — the first pattern that matches — and there is no break needed and no fall-through. If nothing matches and there is no wildcard case, the match statement simply does nothing.
Why does my case comparing against a constant always match?
A bare name in a pattern is a capture, not a comparison — case LIMIT: binds LIMIT to whatever arrives. Compare via a dotted name (case Config.LIMIT:) or a literal. Python usually flags the mistake as a SyntaxError because the capture makes later cases unreachable.
match status:
case HTTPStatus.OK: # dotted name: compares
handle_ok()
Do dict patterns require an exact key match?
No — a dict pattern matches when the listed keys are present with matching values; extra keys are ignored. Sequence patterns are the opposite: [a, b] only matches a two-element sequence. Add *rest to accept longer ones.