Python Basics: Branching on Data Shapes with Pattern Matching
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 11. 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.pyOutput:
Show report
Stop
Unknown commandThe 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.pyOutput:
Resize to 640
Invalid width
Invalid width
Unsupported jobMapping 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.pyOutput:
First: a.txt; remaining: 1
No files supplied
Unknown commandThe 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_dirAcknowledgements
This post was created with assistance from AI.