Structural pattern matching

Python · Advanced · 12 min

Makes parsers, command dispatch and handling of nested JSON-like data far clearer than if-chains.

The match statement (3.10+) compares a value against patterns that describe its shape, binding names to the parts it extracts. The first matching case runs; nothing else does.

Pattern kinds:

  • literal: case 404: or case "quit":
  • capture: case x: matches anything and binds it to x; case _: is the wildcard that binds nothing
  • sequence: case [x, y]: or case [first, *rest]: match lists and tuples (but not strings)
  • mapping: case {"type": "click", "pos": (x, y)}: matches dicts containing those keys; extra keys are allowed
  • class: case Point(x=0, y=y): checks isinstance and then attributes; positional forms like Point(0, y) use the class's __match_args__, which dataclasses generate
  • or-patterns case "y" | "yes":, as to bind a sub-pattern: case [int() as n, *_]:
  • guards: case [x, y] if x == y: add an arbitrary condition

The big gotcha: a bare name is always a capture pattern, never a comparison against a variable. case RED: matches everything and rebinds RED. To compare against a constant, use a dotted name such as Color.RED (an enum member) or a literal.

Pattern matching shines for heterogeneous, nested data: parsed commands, AST nodes, JSON messages, event types. For a simple value switch, a dict lookup or if/elif is often just as clear. Note match isn't an expression; it doesn't return a value.

Example

from dataclasses import dataclass
from enum import Enum

class Color(Enum):
    RED = "red"
@dataclass
class Point:
    x: int
    y: int

def describe(event):
    match event:
        case {"type": "click", "pos": Point(x, y)} if x == y:
            return f"diagonal click at {x}"
        case {"type": "key", "key": "q" | "Q"}:
            return "quit"
        case {"type": "paint", "color": Color.RED}:
            return "painting red"
        case _:
            return "unknown"

print(describe({"type": "click", "pos": Point(3, 3)}))   # diagonal click at 3
print(describe({"type": "key", "key": "Q", "time": 1}))  # quit (extra keys ok)
print(describe({"type": "paint", "color": Color.RED}))   # painting red
print(describe(["a", "b"]))                              # unknown

Remember

  • Patterns match by shape and bind names to the parts
  • Bare names capture; compare to constants with dotted names or literals
  • Mapping patterns ignore extra keys; class patterns use __match_args__

Watch out for

  • Writing case SOME_CONSTANT: expecting a comparison — it captures and always matches.

Check yourself

  1. `STATUS = 200; match code: case STATUS: ...` — what happens when code is 500?
  2. Does `case [a, b]:` match the string "hi"?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/tutorial/controlflow.html#match-statements