Python match-case: branch on the shape of your data
Use Python structural pattern matching for literals, sequences, mappings, and class instances, with guards, capture rules, and explicit fallback behavior.
Structural pattern matching, introduced in Python 3.10, chooses a branch by matching both values and data structure. A case can unpack a sequence, inspect mapping entries, or extract object attributes while binding names for the branch body. It is most useful for events, messages, commands, and syntax trees with distinct shapes.
Use it when the structure makes the decision clearer. A small Boolean condition or dictionary lookup may still be simpler.
Match literal values
def http_status(code):
match code:
case 200:
return "Success"
case 404:
return "Not found"
case 500 | 502 | 503:
return "Server error"
case _:
return "Unknown status"
| combines alternatives; it is not the Boolean or operator. Cases are tried in order, and the first matching case whose guard succeeds runs. There is no switch-style fallthrough into the next case.
_ matches without binding a name. If no case matches and there is no fallback, execution continues after the match statement without raising an error. In a function with no later return, that can mean an implicit None. Literal matching also does not replace type validation: numeric literal patterns use equality, so a value such as 200.0 can match 200.
Match types and extract attributes
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
def area(shape):
match shape:
case bool():
raise TypeError("A Boolean is not an area input")
case int() | float() as number:
return number * number
case str():
return "A string has no area"
case Point(x=0, y=0):
return 0
case Point(x=x, y=y):
return abs(x * y)
case _:
raise TypeError("Unsupported input")
This demonstrates several patterns rather than a general geometry API: a number is treated as a square's side, and a Point's coordinates are treated as dimensions. The string branch deliberately returns a message, so the example is not a uniformly numeric production contract.
int() and float() are class patterns, not constructor calls. They match instances of those types, including subclasses. Since bool is an int subclass, the Boolean case appears first to reject it explicitly.
Point(x=x, y=y) checks the class and binds its attributes to local variables. Dataclass annotations do not enforce runtime field types by themselves; validate the object's data separately if arithmetic requires it. See the match statement reference.
Unpack command sequences
This complete example uses simple handlers so you can run it directly:
def move(direction):
print("Moving", direction)
def pick(item):
print("Picking up", item)
commands = ["go north", "pick key map", "drop a b c d", "quit"]
for command in commands:
match command.split():
case ["quit"]:
break
case ["go", direction]:
move(direction)
case ["pick", *items]:
for item in items:
pick(item)
case ["drop", *rest] if len(rest) > 3:
print("Drop at most three items at a time")
case _:
print("Unsupported command")
The starred pattern collects remaining elements into a list and can collect zero elements. The if clause is a guard: it runs after the structural pattern matches and its bindings are available. Keep guards readable and avoid unexpected side effects.
This example only rejects an oversized drop command; it does not implement dropping up to three items. Those commands reach the fallback until you add their handler.
Sequence patterns work with supported sequence types, not every iterable. They do not consume arbitrary generators or treat str, bytes, and bytearray as character sequences. The explicit split() is what turns each command string into a list here.
Match mapping entries
def handle_click(x, y):
return ("click", x, y)
def submit():
return "submitted"
def dispatch(event):
match event:
case {"type": "click", "x": x, "y": y}:
return handle_click(x, y)
case {"type": "key", "key": "Enter"}:
return submit()
case {"type": str() as event_type}:
return f"Unhandled event: {event_type}"
case _:
return "Invalid event shape"
Mapping patterns are partial: the listed keys and matching values are required, but extra keys are allowed. A click event can include a timestamp without changing this branch. Use **rest to capture additional entries if needed.
The click pattern binds x and y; it does not prove they are valid coordinates. Add class patterns, guards, or an explicit validation layer when values need stronger constraints. Structural dispatch is useful for parsed JSON events, but it is not a schema validator.
Know when another construct is clearer
Use if for a small choice driven by Boolean conditions. Use a mapping and .get() when a value simply selects another value. Complex guards can make a match harder to understand than a direct condition.
Choose match when several shapes need different handling and the variables extracted from each shape belong naturally in that branch. Review case order so a broad pattern does not hide a more specific one.
Common questions
Does a bare name compare with an existing constant?
No. In case RED:, RED is a capture pattern that binds the subject; it is not a lookup of an existing constant. A bare capture without a guard is irrefutable and can make later cases invalid. Use a literal or a dotted value pattern such as Color.RED to compare a named constant. _ is the special wildcard that does not bind.
Does match run faster than if/elif?
There is no universal winner. Choose for clarity first, then benchmark a real hot path with representative inputs if its cost matters.
Can custom classes support positional patterns?
Yes. __match_args__ specifies which attributes correspond to positional pattern arguments. Dataclasses normally generate it for their eligible fields, so the Point above also supports case Point(0, 0). Keyword patterns such as Point(x=0, y=0) avoid depending on positional field order. The PEP 636 tutorial explains these patterns and capture rules.
Diagnose unhandled messages
If a service receives an unknown event shape, log its safe type/category and a request identifier instead of dumping the entire payload. Guance's log collection documentation provides a way to inspect those application records. Validation failures and unsupported event versions can then be distinguished from defects inside a handler.
The examples were run locally, including fallbacks, Boolean rejection, extra mapping keys, and positional dataclass matching. No performance benchmark or Guance ingestion test was performed. Related reading: Start a Python HTTP server (Chinese) and Python scheduling (Chinese).
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan