Skip to content
Python Basics: Branching on Data Shapes with Pattern Matching

Python Basics: Branching on Data Shapes with Pattern Matching

October 19, 2026

When input can have several shapes, match and case can unpack it while choosing the branch. These examples use Python 3.10 or newer and keep unknown input handling explicit.

These three standalone examples were tested on Linux with Python 3.12.14. They require no third-party packages. Assertions check the results; run without Python’s -O option.

Prepare a scratch directory

You need Bash, Python 3.10 or newer on your PATH, a text editor, and the mktemp, rm, and rmdir utilities. Keep using the same terminal and save each named source file as UTF-8.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Match a small command sequence

Save as match_command.py:

def describe(command):
    match command:
        case ["show", name]:
            return f"Show {name}"
        case ["quit"]:
            return "Stop"
        case _:
            return "Unknown command"


results = [describe(["show", "report"]), describe(["quit"]), describe("quit")]
assert results == ["Show report", "Stop", "Unknown command"]
for result in results:
    print(result)

Run it:

python3 match_command.py

Output:

Show report
Stop
Unknown command

The first pattern requires two elements and captures the second as name. Sequence patterns also accept tuples, but do not treat strings as sequences to unpack. The wildcard underscore supplies a fallback.

2. Select mapping fields with a guard

Save as match_job.py:

def describe(job):
    match job:
        case {"kind": "resize", "width": int(width)} if type(width) is int and width > 0:
            return f"Resize to {width}"
        case {"kind": "resize"}:
            return "Invalid width"
        case _:
            return "Unsupported job"


results = [
    describe({"kind": "resize", "width": 640, "source": "sample"}),
    describe({"kind": "resize", "width": -1}),
    describe({"kind": "resize", "width": True}),
    describe({"kind": "copy"}),
]
assert results == ["Resize to 640", "Invalid width", "Invalid width", "Unsupported job"]
for result in results:
    print(result)

Run it:

python3 match_job.py

Output:

Resize to 640
Invalid width
Invalid width
Unsupported job

Mapping patterns allow extra keys. The guard runs after the pattern matches and rejects nonpositive widths. bool is a subclass of int, so the exact-type check deliberately rejects True and False here. int(width) is a class pattern, not a conversion.

3. Capture a sequence tail

Save as match_batch.py:

def summarize(command):
    match command:
        case ["upload" | "send", first, *rest]:
            return f"First: {first}; remaining: {len(rest)}"
        case ["upload" | "send"]:
            return "No files supplied"
        case _:
            return "Unknown command"


assert summarize(["send", "a.txt", "b.txt"]) == "First: a.txt; remaining: 1"
print(summarize(["send", "a.txt", "b.txt"]))
print(summarize(["upload"]))
print(summarize(["delete", "a.txt"]))

Run it:

python3 match_batch.py

Output:

First: a.txt; remaining: 1
No files supplied
Unknown command

The vertical bar accepts either literal command. first requires at least one file; rest captures any remaining elements as a list. Cases are tried in order, and only the first matching case with a true guard runs.

When to use this

Use quoted literals when comparing strings. A bare name in a pattern usually captures a value rather than comparing against an existing variable. Prefer ordinary if statements when there is no useful structure to unpack.

References

Clean up

In the same terminal, remove only these example files, then return to your original directory and remove the empty scratch directory:

rm -- match_command.py match_job.py match_batch.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

Acknowledgements

This post was created with assistance from AI.

Last updated on