match
Not just a switch: patterns test shape and type and pull values out in one step. The trap — a bare name in a case captures anything instead of comparing against a constant.
match status: case 200 | 201: ok() case _: other()
match cmd.split(): case ["go", where]: go(where) case [op, *args] if args: run(op, args)
match event: case {"type": "key", "k": k}: press(k)
match shape: case Point(x=0, y=y): on_y_axis(y) case int(n) | float(n): number(n)
Demo
status = 201 match status: case 200 | 201: text = "OK" case 404: text = "Not Found" case 500 | 502 | 503: text = "Server error" case _: text = "Unknown" text
In the sequence tab, "go north now" is unknown: ["go", direction] needs exactly two words. "take" alone matches the shape ["take", *items] with items == [], but the guard if items is false, so matching moves on. In the capture trap tab, "blue" still prints matched RED and RED itself is now "blue" — the case bound the name instead of comparing. The dotted constant tab is the fix; with more cases after a bare name, Python refuses to compile at all (see the pitfalls).
Syntax slots
| Name | Type | Required | Description |
|---|---|---|---|
| subject | expression | yes | Evaluated once. A comma-separated list without brackets is a tuple: match x, y:. |
| pattern | name / pattern | yes | Literal, capture name, _ wildcard, dotted constant, [sequence], {mapping}, Class(...), p1 | p2, or p as name. |
| guard | expression | no | if condition after the pattern. Checked after the pattern matched and bound its names; false → try the next case. |
| body | block | yes | Runs for the first case that matches. No fall-through, no break needed. |
Common patterns
def handle(msg): match msg: case {"type": "join", "user": str(name)}: return f"{name} joined" case {"type": "say", "user": str(name), "text": text}: return f"{name}: {text}" case _: raise ValueError(f"bad message: {msg!r}")
from dataclasses import dataclass @dataclass class Point: x: float y: float def where(p): match p: case Point(x=0, y=0): return "origin" case Point(x=0, y=y): return f"y-axis at {y}" case Point(x, y): return f"({x}, {y})"
from enum import Enum class Color(Enum): RED = 1 GREEN = 2 def hex_of(c): match c: case Color.RED: return "#f00" case Color.GREEN: return "#0f0"
def describe(v): match v: case bool(b): return f"flag {b}" case int(n) | float(n): return f"number {n}" case str() as s: return f"text {s!r}" case _: return "other"
Examples
Pitfalls
compile('match c:\n case RED:\n pass\n case _:\n pass', '<demo>', 'exec')
class Color: RED = 'red' c = 'blue' match c: case Color.RED: print('red') case _: print('not red')
compile('match v:\n case str:\n pass\n case int():\n pass', '<demo>', 'exec')
v = 42 match v: case str(): print('text') case int(): print('int')
match 'ab': case [a, b]: print(a, b) case _: print('no match')
match list('ab'): case [a, b]: print(a, b)
match True: case 1: print('one') case True: print('true')
match True: case True: print('true') case 1: print('one')
When to use
- Branching on the structure of data — lists of a given length, dicts with certain keys, objects of a type
- Command / message / token dispatch where each branch also unpacks values
- Replacing isinstance + indexing + len checks with one readable pattern
- Code that must run on Python 3.9 or earlier
- A simple value → value table → a dict lookup
- One or two plain comparisons → if / elif
Notes
FAQ
Since 3.10 it has match / case, which covers switch and much more. Cases never fall through, so no break is needed; _ plays the role of default. On older versions use if / elif or a dict lookup.