Python 3.10: Pattern Matching with Match/Case
python dev
Python 3.10 brings structural pattern matching—Python’s take on switch/case. But it’s much more than a switch statement. Here’s what you need to know.
Basic Syntax
def http_status(status):
match status:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return "Unknown"
The _ is the wildcard pattern—it matches anything.
Beyond Switch Statements
Matching Sequences
def handle_point(point):
match point:
case (0, 0):
return "Origin"
case (0, y):
return f"Y-axis at {y}"
case (x, 0):
return f"X-axis at {x}"
case (x, y):
return f"Point at ({x}, {y})"
handle_point((0, 0)) # "Origin"
handle_point((0, 5)) # "Y-axis at 5"
handle_point((3, 4)) # "Point at (3, 4)"
Variable Capture
Variables in patterns capture the matched value:
def process_command(command):
match command.split():
case ["quit"]:
return "Goodbye"
case ["load", filename]:
return f"Loading {filename}"
case ["save", filename]:
return f"Saving to {filename}"
case ["move", direction, distance]:
return f"Moving {direction} by {distance}"
case _:
return "Unknown command"
process_command("load data.csv") # "Loading data.csv"
process_command("move north 10") # "Moving north by 10"
Matching Dictionaries
def handle_event(event):
match event:
case {"type": "click", "x": x, "y": y}:
return f"Click at ({x}, {y})"
case {"type": "keypress", "key": key}:
return f"Key pressed: {key}"
case {"type": "scroll", "direction": dir}:
return f"Scroll {dir}"
case _:
return "Unknown event"
handle_event({"type": "click", "x": 100, "y": 200}) # "Click at (100, 200)"
Class Patterns
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
@dataclass
class Circle:
center: Point
radius: int
@dataclass
class Rectangle:
top_left: Point
bottom_right: Point
def describe_shape(shape):
match shape:
case Circle(center=Point(0, 0), radius=r):
return f"Circle at origin with radius {r}"
case Circle(center=c, radius=r):
return f"Circle at {c} with radius {r}"
case Rectangle(top_left=Point(x1, y1), bottom_right=Point(x2, y2)):
return f"Rectangle from ({x1},{y1}) to ({x2},{y2})"
case _:
return "Unknown shape"
Guards
Add conditions with if:
def classify_number(n):
match n:
case n if n < 0:
return "Negative"
case 0:
return "Zero"
case n if n % 2 == 0:
return "Positive even"
case _:
return "Positive odd"
OR Patterns
Match multiple patterns:
def handle_response(response):
match response:
case 200 | 201 | 204:
return "Success"
case 400 | 401 | 403 | 404:
return "Client error"
case 500 | 502 | 503:
return "Server error"
case _:
return "Unknown"
AS Pattern
Capture while matching:
def process_data(data):
match data:
case {"user": {"name": name, "email": email} as user_data}:
return f"User: {name}, Full data: {user_data}"
Real-World Examples
API Response Handling
def handle_api_response(response):
match response:
case {"status": "success", "data": data}:
return process_data(data)
case {"status": "error", "code": code, "message": msg}:
log_error(code, msg)
return None
case {"status": "pending", "job_id": job_id}:
return poll_job(job_id)
case _:
raise ValueError("Invalid response format")
Command-Line Argument Parsing
def parse_args(args):
match args:
case []:
return "No arguments"
case ["-h"] | ["--help"]:
return show_help()
case ["-v"] | ["--version"]:
return show_version()
case ["-f", filename]:
return process_file(filename)
case ["-f", filename, "-o", output]:
return process_file(filename, output)
case _:
return "Invalid arguments"
State Machine
@dataclass
class State:
name: str
data: dict = None
def handle_state(state, event):
match (state.name, event):
case ("idle", "start"):
return State("running")
case ("running", "pause"):
return State("paused")
case ("paused", "resume"):
return State("running")
case ("running" | "paused", "stop"):
return State("idle")
case _:
return state # No transition
JSON Parsing
def parse_config(config):
match config:
case {"database": {"host": host, "port": port, **db_opts}}:
return DatabaseConfig(host, port, **db_opts)
case {"database": str() as url}:
return DatabaseConfig.from_url(url)
case {"database": None}:
return DatabaseConfig.default()
case _:
raise ConfigError("Invalid database configuration")
Pattern Types Summary
| Pattern | Example | Matches |
|---|---|---|
| Literal | 42, "hello" | Exact value |
| Capture | x, name | Any value, binds to variable |
| Wildcard | _ | Any value, no binding |
| Sequence | [a, b, c] | Lists/tuples with pattern |
| Mapping | {"key": value} | Dict with pattern |
| Class | Point(x, y) | Class instances |
| OR | a | b | Either pattern |
| AS | pattern as name | Pattern + capture |
| Guard | case x if x > 0 | Pattern with condition |
Tips
Use with Dataclasses
@dataclass
class User:
name: str
role: str
active: bool = True
# Pattern matching works naturally
match user:
case User(role="admin", active=True):
return "Active admin"
Avoid Overuse
Simple if/elif often clearer:
# Match: overkill
match x:
case 1:
return "one"
case 2:
return "two"
# Better as dict
{1: "one", 2: "two"}.get(x)
Match is Exhaustive
No fallthrough; only first match executes.
Final Thoughts
Pattern matching is Python’s most significant syntax addition in years. It shines for:
- Complex data parsing
- State machines
- AST handling
- Structured data validation
Learn the patterns. Use where it clarifies. Skip where it complicates.
Match what you mean.