Python Structural Pattern Matching

Python structural pattern matching lets you test a value's shape and contents in one clear statement. The match statement evaluates a subject, then checks case patterns from top to bottom until the first one matches.

Python added this feature in version 3.10. It handles more than constant comparisons: a pattern can unpack sequences, read mapping keys, test class attributes, combine alternatives, and capture values for the selected branch.

Basic Match and Case Syntax

Use match with one or more case blocks. The underscore pattern matches anything and works as a final fallback.

Example:

def order_status(code):
    # Cases are tested from top to bottom.
    match code:
        case 200:
            return "Order confirmed"
        case 202:
            return "Order is being prepared"
        case 404:
            return "Order not found"
        case _:
            return "Unknown status"

print(order_status(202))

Match Several Literal Values

Join alternatives with | when different values should run the same block.

Example:

def access_message(role):
    # All three literal values share one result.
    match role:
        case "admin" | "editor" | "author":
            return "Content access granted"
        case "reader":
            return "Read-only access"
        case _:
            return "Access denied"

Match and Capture Sequences

A sequence pattern checks the number and position of items while capturing selected values. It matches sequence types such as lists and tuples, but it does not treat strings as sequences for this purpose.

Example:

def route_command(words):
    match words:
        case ["go", direction]:
            # Capture the second item as direction.
            return f"Moving {direction}"
        case ["add", item, *extras]:
            # Capture all remaining items in a list.
            return f"Adding {item}; extras: {extras}"
        case ["stop"]:
            return "Route stopped"
        case _:
            return "Invalid command"

print(route_command(["go", "north"]))

Match Dictionary Data

A mapping pattern checks required keys and captures their values. Extra keys are allowed unless you capture them with **rest.

Example:

def describe_booking(booking):
    match booking:
        case {"status": "confirmed", "passenger": name, **details}:
            # name receives the passenger value.
            return f"Confirmed for {name}; extra fields: {details}"
        case {"status": "cancelled", "reason": reason}:
            return f"Cancelled: {reason}"
        case {"status": status}:
            return f"Current status: {status}"
        case _:
            return "Invalid booking data"

Add a Guard

A guard is an if condition after a pattern. Python checks the guard only after the pattern matches and captures its variables.

Example:

def delivery_fee(order):
    match order:
        case {"city": city, "total": total} if total >= 1000:
            # Free delivery applies only after both keys match.
            return f"Free delivery in {city}"
        case {"city": city, "total": total}:
            return f"Standard delivery in {city} for order {total}"
        case _:
            return "Order data is incomplete"

Match Class Attributes

A class pattern tests the object's type and matches named attributes. Dataclasses and named tuples also support useful positional patterns.

Example:

from dataclasses import dataclass

@dataclass
class Payment:
    method: str
    amount: float

def payment_message(value):
    match value:
        case Payment(method="card", amount=amount) if amount > 5000:
            # Match the type, selected attribute, and guard.
            return f"Verify high-value card payment: {amount}"
        case Payment(method=method, amount=amount):
            return f"{method.title()} payment: {amount}"
        case _:
            return "Unsupported payment"

Capture a Whole Subpattern

Use as when you need both a matched part and the full value.

Example:

def validate_point(point):
    match point:
        case (0, y) as vertical_point:
            # Capture y and the complete tuple.
            return f"On the Y-axis: {vertical_point}, y={y}"
        case (x, 0) as horizontal_point:
            return f"On the X-axis: {horizontal_point}, x={x}"
        case (x, y):
            return f"Point at {x}, {y}"
        case _:
            return "Not a two-dimensional point"

Understand Capture Patterns

A bare name in a pattern captures a value; it does not compare against an existing variable. Use literals or qualified names such as Status.READY when you need a constant comparison.

Example:

from enum import Enum

class Status(Enum):
    READY = "ready"
    CLOSED = "closed"

def handle(status):
    match status:
        # A qualified enum name is treated as a value pattern.
        case Status.READY:
            return "Start processing"
        case Status.CLOSED:
            return "No more work"

Common Mistakes

  • Place specific patterns before broad patterns because only the first match runs.
  • Keep case _ last; an earlier wildcard would make later cases unreachable.
  • Do not use an unqualified variable name when you intend to compare a constant.
  • Make every alternative in an OR pattern bind the same set of names.
  • Use if statements when you only need simple Boolean conditions; pattern matching is most useful when structure matters.

Tip: Start with the shape of the data. If several branches unpack the same lists, dictionaries, or objects differently, a match statement can make the flow easier to read.

Conclusion

Python structural pattern matching combines selection, validation, and unpacking. Use literal patterns for fixed values, sequence and mapping patterns for structured data, class patterns for objects, and guards for extra conditions. Order cases from most specific to most general and keep the wildcard fallback last.



Found This Page Useful? Share It!
Get the Latest Tutorials and Updates
Join us on Telegram