By the end of this lesson, you will be able to use Python's match and case statements to simplify complex conditional logic by matching data structures directly.
What it is
Structural pattern matching, introduced in Python 3.10, allows you to check if a value matches a specific shape or structure. Unlike traditional if/elif chains that often rely on multiple boolean checks, match/case lets you deconstruct data types like lists, dictionaries, and custom objects in a single statement. The key mental model is "shape matching": you define what the data should look like, and Python handles the extraction and validation.
Related terms include patterns (the shapes you match against), guards (additional conditions using if within a case), and wildcards (using _ to match anything).
Why it matters
- Readability: It reduces nested
ifstatements, making code easier to scan and understand. - Data Extraction: You can bind variables directly from the matched structure without manual indexing or key access.
- Safety: It encourages handling all possible cases explicitly, reducing bugs from unhandled edge cases.
- Conciseness: Complex checks for type and content are combined into one line per case.
Syntax or steps
The basic syntax follows this structure:
match subject:
case pattern_1:
# code block
case pattern_2:
# code block
case _:
# default catch-all
The subject is evaluated once. Each pattern is checked in order. If a pattern matches, its associated code runs, and the rest of the cases are skipped. The underscore _ acts as a wildcard that always matches, serving as an else clause.
Example
Consider processing user commands where some require arguments. This example demonstrates literal matching, sequence unpacking, and guards.
def handle_command(command):
match command.split():
case ["quit"]:
print("Exiting...")
case ["hello", name]:
print(f"Hello, {name}!")
case ["add", x, y] if int(x) > 0 and int(y) > 0:
print(f"Sum: {int(x) + int(y)}")
case ["add", *numbers]:
print(f"Adding {len(numbers)} numbers.")
case _:
print("Unknown command")
# Test cases
handle_command("quit")
handle_command("hello Alice")
handle_command("add 5 10")
handle_command("add 1 2 3")
handle_command("fly me to the moon")
Explanation:
command.split()converts the string into a list of words, which we then match against patterns.case ["quit"]:matches exactly when the list contains only the string "quit".case ["hello", name]:matches any two-element list starting with "hello" and binds the second element to the variablename.case ["add", x, y] if ...:uses a guard (if) to ensure additional constraints are met before executing the block.case ["add", *numbers]:uses star-unpacking to capture any number of remaining elements into a list callednumbers.case _:catches any input that didn't match previous patterns.
Common mistakes
- Forgetting the wildcard: Without
case _:, unmatched inputs silently do nothing. Always include a default case unless you intentionally want no action. - Confusing literals with variables: In
case x:,xis treated as a variable binding (capturing everything), not a comparison to a variable namedx. To compare against a variable, use a guard:case val if val == x:. - Ignoring order: Patterns are checked top-to-bottom. Place more specific patterns before general ones to avoid unintended matches.
- Version mismatch: Ensure your environment is running Python 3.10 or newer; older versions will raise a SyntaxError.
When to use it
Use match/case when dealing with structured data or complex branching based on type/content. Use if/elif for simple boolean logic or when compatibility with older Python versions is required.
| Feature | Match Case | If / Elif |
|---|---|---|
| Best For | Deconstructing lists, dicts, tuples | Simple boolean checks |
| Variable Binding | Automatic during match | Manual assignment required |
| Python Version | 3.10+ | All versions |
Practice
Guided Exercise: Write a function that takes a tuple representing a point (x, y). Use match to print "Origin" if both are 0, "On X-axis" if y is 0, "On Y-axis" if x is 0, and "Quadrant" otherwise.
Challenge: Extend the exercise to handle a dictionary format {"type": "point", "coords": [x, y]}. Match the structure and extract coordinates.
Hint: For the challenge, use nested patterns: case {"type": "point", "coords": [x, y]}:.
Quick check
Question: What does the underscore _ represent in a case statement?
Answer: It represents a wildcard pattern that matches any value, effectively acting as the default or "else" case.
Summary
Python's structural pattern matching provides a powerful way to handle complex data shapes cleanly and concisely. By mastering match/case, you can write more readable code that safely extracts values while enforcing structure, significantly improving maintainability in modern Python applications.