Python Match
Learn about the match statement in Python 3.10+ - a powerful pattern matching feature.
Python Match Statement
The match statement was introduced in Python 3.10. It provides a way to compare a value against several patterns and execute code based on which pattern matches.
The match statement is similar to switch statements in other programming languages, but much more powerful due to pattern matching capabilities.
Basic Example
def check_value(x):
match x:
case 1:
return "One"
case 2:
return "Two"
case 3:
return "Three"
case _:
return "Something else"
print(check_value(2)) # Output: Two
print(check_value(5)) # Output: Something elseBasic Syntax
The basic syntax of a match statement consists of:
- The
matchkeyword followed by an expression - One or more
caseclauses with patterns - An optional wildcard case using
_
Example
status = 404
match status:
case 200:
print("OK")
case 404:
print("Not Found")
case 500:
print("Internal Server Error")
case _:
print("Unknown status")Pattern Matching with Multiple Values
You can match against multiple values in a single case:
Example
def check_grade(grade):
match grade:
case 'A' | 'B':
return "Excellent"
case 'C' | 'D':
return "Good"
case 'F':
return "Fail"
case _:
return "Invalid grade"
print(check_grade('A')) # Output: Excellent
print(check_grade('C')) # Output: GoodPattern Matching with Conditions
You can add conditions to patterns using the if keyword:
Example
def categorize_number(x):
match x:
case n if n < 0:
return "Negative"
case 0:
return "Zero"
case n if n > 100:
return "Large positive"
case n if n > 0:
return "Small positive"
print(categorize_number(-5)) # Output: Negative
print(categorize_number(150)) # Output: Large positiveMatching Lists and Tuples
Match statements can destructure sequences like lists and tuples:
Example
def analyze_point(point):
match point:
case [0, 0]:
return "Origin"
case [0, y]:
return f"On Y-axis at {y}"
case [x, 0]:
return f"On X-axis at {x}"
case [x, y]:
return f"Point at ({x}, {y})"
case _:
return "Not a 2D point"
print(analyze_point([0, 0])) # Output: Origin
print(analyze_point([3, 0])) # Output: On X-axis at 3
print(analyze_point([2, 5])) # Output: Point at (2, 5)Matching Dictionaries
You can match against dictionary patterns:
Example
def process_request(request):
match request:
case {"action": "get", "resource": resource}:
return f"Getting {resource}"
case {"action": "post", "resource": resource, "data": data}:
return f"Posting to {resource}: {data}"
case {"action": "delete", "resource": resource}:
return f"Deleting {resource}"
case _:
return "Invalid request"
req1 = {"action": "get", "resource": "users"}
req2 = {"action": "post", "resource": "posts", "data": "Hello World"}
print(process_request(req1)) # Output: Getting users
print(process_request(req2)) # Output: Posting to posts: Hello WorldMatching Objects and Classes
Match statements can work with custom classes and objects:
Example
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def describe_point(point):
match point:
case Point(x=0, y=0):
return "Origin point"
case Point(x=0, y=y):
return f"Point on Y-axis at y={y}"
case Point(x=x, y=0):
return f"Point on X-axis at x={x}"
case Point(x=x, y=y) if x == y:
return f"Point on diagonal at ({x}, {y})"
case Point(x=x, y=y):
return f"Point at ({x}, {y})"
p1 = Point(0, 0)
p2 = Point(3, 3)
p3 = Point(5, 2)
print(describe_point(p1)) # Output: Origin point
print(describe_point(p2)) # Output: Point on diagonal at (3, 3)
print(describe_point(p3)) # Output: Point at (5, 2)Capturing Values
You can capture parts of the matched pattern into variables:
Example
def process_data(data):
match data:
case [first, *rest]:
return f"First: {first}, Rest: {rest}"
case {"name": name, "age": age, **others}:
return f"Person: {name}, Age: {age}, Other info: {others}"
case str() as text if len(text) > 10:
return f"Long string: {text[:10]}..."
case _:
return "Unknown data format"
print(process_data([1, 2, 3, 4]))
# Output: First: 1, Rest: [2, 3, 4]
print(process_data({"name": "Alice", "age": 30, "city": "NYC"}))
# Output: Person: Alice, Age: 30, Other info: {'city': 'NYC'}
print(process_data("This is a very long string"))
# Output: Long string: This is a ...Practical Example: Calculator
Here's a practical example of using match for a simple calculator:
Example
def calculate(operation):
match operation:
case ("add", x, y):
return x + y
case ("subtract", x, y):
return x - y
case ("multiply", x, y):
return x * y
case ("divide", x, y) if y != 0:
return x / y
case ("divide", x, 0):
return "Error: Division by zero"
case ("power", x, y):
return x ** y
case _:
return "Unknown operation"
print(calculate(("add", 5, 3))) # Output: 8
print(calculate(("divide", 10, 2))) # Output: 5.0
print(calculate(("divide", 10, 0))) # Output: Error: Division by zero
print(calculate(("power", 2, 3))) # Output: 8Match vs If-Elif-Else
While match statements are powerful, they're not always necessary. Here's when to use each:
Use Match When:
- Pattern matching complex data structures
- Destructuring sequences or objects
- Multiple conditions on the same value
- Working with structured data
Use If-Elif-Else When:
- Simple boolean conditions
- Comparing different variables
- Complex logical expressions
- Backward compatibility (Python < 3.10)
Structural Pattern Matching
match (Python 3.10+) is far more than a switch — it can destructure data. The wildcard _ is the catch-all default.
def http_status(code):
match code:
case 200 | 201: # multiple values with |
return "Success"
case 404:
return "Not Found"
case n if n >= 500: # guard condition
return "Server Error"
case _: # default
return "Unknown"
print(http_status(503)) # Server ErrorMatching Structure
point = (0, 5)
match point:
case (0, 0):
print("origin")
case (0, y):
print(f"on the y-axis at {y}") # captures y = 5
case (x, 0):
print(f"on the x-axis at {x}")
case (x, y):
print(f"at {x}, {y}")A bare name in a pattern (like y) captures the value. Use a literal or case Color.RED to match against a constant.
Try It Yourself
Exercise 1: Write a match that returns "weekend" for "Sat" or "Sun", else "weekday".
Show solution
def kind(day):
match day:
case "Sat" | "Sun":
return "weekend"
case _:
return "weekday"
print(kind("Sun")) # weekendExercise 2: Match a 2-tuple and print whether it lies on the x-axis (y == 0).
Show solution
match (7, 0):
case (x, 0):
print(f"on x-axis at {x}") # on x-axis at 7
case _:
print("elsewhere")Key Takeaways
match/case(3.10+) matches values and structure.- Combine patterns with
|, add conditions withifguards. _is the default; bare names capture values.
📘 Real-World Deep Dive
The <code>match</code>/<code>case</code> statement (Python 3.10+) lets you destructure sequences, mappings, and primitives with pattern syntax. It shines when you have a nested, polymorphic data structure.
Real-Life Scenario
A small event-router: parse messages from a queue into well-typed events, dispatch by type, and ignore anything malformed.
Real-Life Example
import json
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True)
class OrderCreated: id: int; amount: float
@dataclass(frozen=True)
class OrderPaid: id: int; method: str
@dataclass(frozen=True)
class UserSignedUp: user: str
class UnknownEvent: pass
def parse(raw: dict) -> Any:
match raw:
case {"type": "order_created", "id": int(id_), "amount": float(amount)}:
return OrderCreated(id_, amount)
case {"type": "order_paid", "id": int(id_), "method": str(method)}:
return OrderPaid(id_, method)
case {"type": "user_signup", "user": str(user)}:
return UserSignedUp(user)
case _:
return UnknownEvent()
def handle(event):
match event:
case OrderCreated(_, amount) if amount > 100_000:
print("alert: large order", event)
case OrderCreated():
print("ack order", event)
case OrderPaid(_, "wire"):
print("reconcile wire")
case OrderPaid(_, _):
print("reconcile card")
case UserSignedUp(u):
print("send welcome to", u)
case UnknownEvent():
pass
samples = [
{"type": "order_created", "id": 42, "amount": 250},
{"type": "order_created", "id": 7, "amount": 250_000.25},
{"type": "order_paid", "id": 42, "method": "wire"},
{"type": "user_signup", "user": "ada"},
{"type": "what?"},
]
for raw in samples:
handle(parse(raw))Expected Output
ack order OrderCreated(id=42, amount=250)
alert: large order OrderCreated(id=7, amount=250000.25)
reconcile wire
send welcome to adaCommon mistakes
- Default pattern matches anything; write explicit
case _:arms to handle unmatched cases. - A pattern that catches by type but doesn't pin a value can mask bugs — prefer explicit binding.
- Capturing names like
"_"don't bind: use_for "I don't care",varnamefor binding.
🚀 Performance & Best Practices
matchcompiles to a checklist of patterns; it's no slower than a cleanif/elifchain.- Use guards (
case X if cond:) sparingly — a hot guard can add real overhead. - Nested destructuring is faster than manual
if isinstance(...)chains for typed dispatch.
🧪 Try It Yourself
- Add an
OrderRefundedevent and route it to a separate handler. - Replace the parser with one that auto-discovers event types via a registry.
- Add a pytest test suite that fuzzes the JSON payloads.