Python Match Statement
Master structural pattern matching in Python 3.10+
šÆ What is Pattern Matching?
The match statement, introduced in Python 3.10, provides powerful pattern matching capabilities. Similar to switch statements in other languages, it lets you compare a value against multiple patterns and execute code based on which pattern matches.
# Simple match example
def get_food(food):
match food:
case "pizza":
return "$20"
case "burger":
return "$15"
case "salad":
return "$5"
case _:
return "Unknown food"
Basic Match Statement
The simplest form of match compares a value against literal patterns:
# Basic match statement
def check_status_code(code):
match code:
case 200:
return "OK - Request successful"
case 404:
return "Not Found - Resource not found"
case 500:
return "Internal Server Error"
case _: # Wildcard pattern (like 'else')
return f"Unknown status code: {code}"
# Test the function
print(check_status_code(200)) # OK - Request successful
print(check_status_code(404)) # Not Found - Resource not found
print(check_status_code(999)) # Unknown status code: 999
š” Key Points:
-
The
_(underscore) is a wildcard that matches anything - Cases are checked in order from top to bottom
- Only the first matching case is executed
Matching Multiple Values
You can match multiple values using the OR operator (
|
):
def categorize_day(day):
match day.lower():
case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
return "Weekday - Time to work!"
case "saturday" | "sunday":
return "Weekend - Time to relax!"
case _:
return "Invalid day"
# Test the function
print(categorize_day("Monday")) # Weekday - Time to work!
print(categorize_day("Saturday")) # Weekend - Time to relax!
print(categorize_day("Funday")) # Invalid day
# Matching numbers
def classify_grade(score):
match score:
case 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100:
return "A - Excellent!"
case score if 80 <= score <= 89:
return "B - Good job!"
case score if 70 <= score <= 79:
return "C - Average"
case score if 60 <= score <= 69:
return "D - Needs improvement"
case _:
return "F - Failed"
print(classify_grade(95)) # A - Excellent!
print(classify_grade(75)) # C - Average
Capture Patterns and Guards
You can capture matched values and use guards (if conditions) for more complex matching:
# Capture patterns with guards
def analyze_number(num):
match num:
case 0:
return "Zero"
case n if n > 0 and n < 10:
return f"Single positive digit: {n}"
case n if n < 0 and n > -10:
return f"Single negative digit: {n}"
case n if n >= 10:
return f"Large positive number: {n}"
case n if n <= -10:
return f"Large negative number: {n}"
# Test the function
print(analyze_number(0)) # Zero
print(analyze_number(5)) # Single positive digit: 5
print(analyze_number(-3)) # Single negative digit: -3
print(analyze_number(25)) # Large positive number: 25
print(analyze_number(-15)) # Large negative number: -15
# Age categorization with guards
def categorize_age(age):
match age:
case a if a < 0:
return "Invalid age"
case a if 0 <= a <= 12:
return f"Child (age {a})"
case a if 13 <= a <= 19:
return f"Teenager (age {a})"
case a if 20 <= a <= 64:
return f"Adult (age {a})"
case a if a >= 65:
return f"Senior (age {a})"
print(categorize_age(8)) # Child (age 8)
print(categorize_age(16)) # Teenager (age 16)
print(categorize_age(30)) # Adult (age 30)
Sequence Pattern Matching
Match against lists, tuples, and other sequences with powerful destructuring:
# Matching lists and tuples
def process_coordinates(point):
match point:
case []:
return "Empty list"
case [x]:
return f"1D point at {x}"
case [x, y]:
return f"2D point at ({x}, {y})"
case [x, y, z]:
return f"3D point at ({x}, {y}, {z})"
case [x, y, z, *rest]:
return f"Multi-dimensional point starting at ({x}, {y}, {z}) with {len(rest)} more dimensions"
# Test with different coordinate types
print(process_coordinates([])) # Empty list
print(process_coordinates([5])) # 1D point at 5
print(process_coordinates([3, 4])) # 2D point at (3, 4)
print(process_coordinates([1, 2, 3])) # 3D point at (1, 2, 3)
print(process_coordinates([1, 2, 3, 4, 5])) # Multi-dimensional point...
# Command processing system
def process_command(command):
match command:
case ["quit"] | ["exit"]:
return "Goodbye!"
case ["help"]:
return "Available commands: move, create, delete, help, quit"
case ["move", direction] if direction in ["up", "down", "left", "right"]:
return f"Moving {direction}"
case ["move", x, y] if isinstance(x, int) and isinstance(y, int):
return f"Moving to position ({x}, {y})"
case ["create", item_type, name]:
return f"Creating {item_type} named '{name}'"
case ["delete", name]:
return f"Deleting '{name}'"
case _:
return "Unknown command. Type 'help' for available commands."
# Test command processing
print(process_command(["quit"])) # Goodbye!
print(process_command(["move", "up"])) # Moving up
print(process_command(["move", 10, 20])) # Moving to position (10, 20)
print(process_command(["create", "file", "test.txt"])) # Creating file named 'test.txt'
print(process_command(["invalid"])) # Unknown command...
Dictionary and Object Pattern Matching
Match against dictionaries and object attributes for complex data processing:
# Dictionary pattern matching
def process_user_data(user):
match user:
case {"name": name, "age": age, "role": "admin"} if age >= 18:
return f"Admin {name} (age {age}) has full access"
case {"name": name, "age": age, "role": "user"} if age >= 18:
return f"User {name} (age {age}) has limited access"
case {"name": name, "age": age} if age < 18:
return f"{name} (age {age}) requires parental consent"
case {"error": error_msg}:
return f"Error: {error_msg}"
case _:
return "Invalid user data format"
# Test user data processing
admin_user = {"name": "Alice", "age": 30, "role": "admin"}
regular_user = {"name": "Bob", "age": 25, "role": "user"}
minor_user = {"name": "Charlie", "age": 16}
error_data = {"error": "User not found"}
print(process_user_data(admin_user)) # Admin Alice (age 30) has full access
print(process_user_data(regular_user)) # User Bob (age 25) has limited access
print(process_user_data(minor_user)) # Charlie (age 16) requires parental consent
print(process_user_data(error_data)) # Error: User not found
# API response handler
def handle_api_response(response):
match response:
case {"status": "success", "data": data, "count": count}:
return f"Success: Retrieved {count} items"
case {"status": "success", "data": data}:
return f"Success: Retrieved data of type {type(data).__name__}"
case {"status": "error", "code": 404, "message": msg}:
return f"Not Found: {msg}"
case {"status": "error", "code": code, "message": msg}:
return f"Error {code}: {msg}"
case {"status": status}:
return f"Unknown status: {status}"
case _:
return "Invalid response format"
# Test API responses
success_response = {"status": "success", "data": [1, 2, 3], "count": 3}
error_response = {"status": "error", "code": 500, "message": "Internal server error"}
not_found = {"status": "error", "code": 404, "message": "Resource not found"}
print(handle_api_response(success_response)) # Success: Retrieved 3 items
print(handle_api_response(error_response)) # Error 500: Internal server error
print(handle_api_response(not_found)) # Not Found: Resource not found
Real-World Example: HTTP Request Router
Here's a comprehensive example showing how match statements can be used to build a simple HTTP request router:
"""
HTTP Request Router using Python Match Statements
Demonstrates advanced pattern matching for web request handling
"""
def route_request(request):
"""
Route HTTP requests based on method, path, and parameters
"""
match request:
# Home page
case {"method": "GET", "path": "/"}:
return {"status": 200, "body": "Welcome to our website!"}
# User profile pages
case {"method": "GET", "path": path} if path.startswith("/user/"):
user_id = path.split("/")[-1]
if user_id.isdigit():
return {"status": 200, "body": f"User profile for ID: {user_id}"}
else:
return {"status": 400, "body": "Invalid user ID"}
# API endpoints
case {"method": "GET", "path": "/api/users", "params": {"page": page}} if page.isdigit():
return {"status": 200, "body": f"Users page {page}"}
case {"method": "GET", "path": "/api/users"}:
return {"status": 200, "body": "All users (page 1)"}
case {"method": "POST", "path": "/api/users", "body": {"name": name, "email": email}}:
return {"status": 201, "body": f"Created user: {name} ({email})"}
case {"method": "PUT", "path": path, "body": body} if path.startswith("/api/users/"):
user_id = path.split("/")[-1]
return {"status": 200, "body": f"Updated user {user_id}"}
case {"method": "DELETE", "path": path} if path.startswith("/api/users/"):
user_id = path.split("/")[-1]
return {"status": 200, "body": f"Deleted user {user_id}"}
# File uploads
case {"method": "POST", "path": "/upload", "files": files} if files:
file_count = len(files)
return {"status": 200, "body": f"Uploaded {file_count} file(s)"}
# Authentication
case {"method": "POST", "path": "/login", "body": {"username": user, "password": pwd}}:
# In real app, you'd validate credentials
return {"status": 200, "body": f"Login successful for {user}"}
case {"method": "POST", "path": "/logout", "headers": {"Authorization": token}}:
return {"status": 200, "body": "Logout successful"}
# Error cases
case {"method": method} if method not in ["GET", "POST", "PUT", "DELETE"]:
return {"status": 405, "body": "Method not allowed"}
case {"path": path}:
return {"status": 404, "body": f"Path not found: {path}"}
case _:
return {"status": 400, "body": "Bad request format"}
# Test the router with various requests
test_requests = [
{"method": "GET", "path": "/"},
{"method": "GET", "path": "/user/123"},
{"method": "GET", "path": "/user/abc"},
{"method": "GET", "path": "/api/users"},
{"method": "GET", "path": "/api/users", "params": {"page": "2"}},
{"method": "POST", "path": "/api/users", "body": {"name": "John", "email": "john@example.com"}},
{"method": "PUT", "path": "/api/users/123", "body": {"name": "John Updated"}},
{"method": "DELETE", "path": "/api/users/123"},
{"method": "POST", "path": "/upload", "files": ["file1.txt", "file2.jpg"]},
{"method": "POST", "path": "/login", "body": {"username": "admin", "password": "secret"}},
{"method": "PATCH", "path": "/api/users/123"}, # Unsupported method
{"method": "GET", "path": "/nonexistent"}, # 404 case
]
print("š HTTP REQUEST ROUTER DEMO")
print("=" * 50)
for i, request in enumerate(test_requests, 1):
response = route_request(request)
method = request.get("method", "UNKNOWN")
path = request.get("path", "UNKNOWN")
status = response["status"]
body = response["body"]
print(f"{i:2d}. {method:6} {path:20} ā {status} {body}")
# Advanced pattern matching with nested structures
def analyze_log_entry(log_entry):
"""
Analyze log entries with complex nested patterns
"""
match log_entry:
case {
"timestamp": ts,
"level": "ERROR",
"source": {"service": service, "instance": instance},
"error": {"type": error_type, "message": msg}
}:
return f"šØ ERROR in {service}[{instance}]: {error_type} - {msg}"
case {
"timestamp": ts,
"level": "WARNING",
"source": {"service": service},
"message": msg
}:
return f"ā ļø WARNING from {service}: {msg}"
case {
"timestamp": ts,
"level": "INFO",
"user_action": {"user_id": uid, "action": action, "resource": resource}
}:
return f"ā¹ļø User {uid} performed {action} on {resource}"
case {"level": level, "message": msg}:
return f"š {level}: {msg}"
case _:
return "ā Unknown log format"
# Test log analysis
log_entries = [
{
"timestamp": "2024-01-15T10:30:00Z",
"level": "ERROR",
"source": {"service": "auth-service", "instance": "auth-01"},
"error": {"type": "DatabaseConnectionError", "message": "Connection timeout"}
},
{
"timestamp": "2024-01-15T10:31:00Z",
"level": "WARNING",
"source": {"service": "payment-service"},
"message": "High memory usage detected"
},
{
"timestamp": "2024-01-15T10:32:00Z",
"level": "INFO",
"user_action": {"user_id": "user123", "action": "purchase", "resource": "product456"}
}
]
print("\nš LOG ANALYSIS DEMO")
print("=" * 50)
for i, entry in enumerate(log_entries, 1):
analysis = analyze_log_entry(entry)
print(f"{i}. {analysis}")
š§ Test Your Knowledge
Test your understanding of Python match statements: