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"
                                    
3.10+
Python Version
Pattern
Matching
Powerful
Control Flow

Basic Match Statement

The simplest form of match compares a value against literal patterns:

Basic Match Example
# 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 ( | ):

OR Patterns
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
# 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:

Sequence Patterns
# 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 Patterns
# 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
"""
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:

Question 1: What does the underscore (_) represent in a match statement?

Question 2: Which Python version introduced the match statement?

Question 3: What is the purpose of guards in match statements?