Pattern Matching with match-case
Concepts covered: Pattern Matching
Dispatch means a single value decides which of several actions runs. A loader that calls an API keys everything off the response status. It loads on 200 or 204 and skips on 404. A 429 or any 5xx means retry, and anything else raises an alert. The cleaner that ladder reads, the less likely someone adds a new status in the wrong place.
A dispatch ladder in 3.9
def route(status):
if status is None:
return "alert: no status"
if status == 200 or status == 204:
return "load"
if status == 404:
return "skip"
if status == 429 or 500 <= status < 600:
return "retry"
return "alert: unexpected"
print(route(204))
print(route(503))
print(route(301))
print(route(None))Output:
load retry alert: unexpected alert: no status
Every test looks at the same subject, status, so a reader knows the whole ladder is one decision. Each test is also a complete case: an equality, alternatives joined by or, or a chained range. The last return is a deliberate catch-all, so a 301 nobody planned for produces a visible alert instead of reaching the end of the function and returning None.
Alternatives in a single branch are joined with or: status == 200 or status == 204 is True when either equality holds. Joining them with and instead produces a branch that can never run, because 1 status can't equal 200 and 204 at the same time. Python raises no error for it. Every 204 falls through to the next test, and in this ladder that means the catch-all, so a successful response gets reported as unexpected.
The same decision as match-case
| Pattern kind | match-case (3.10+) | Python 3.9 equivalent | Matches when |
|---|---|---|---|
| Literal | `case 404:` | `if status == 404:` | the subject equals 404 |
| Alternatives | `case 200 | 204:` | `if status == 200 or status == 204:` | any listed literal equals the subject |
| Capture plus guard | `case s if 500 <= s < 600:` | `if 500 <= status < 600:` | the guard is True; `s` is bound to the subject |
| None literal | `case None:` | `if status is None:` | the subject is None, compared by identity |
| Wildcard | `case _:` | the final `return` or `else:` | always; it catches whatever is left |
A match statement evaluates its subject once, then tries each case from top to bottom. The first case that fits runs its block and the statement ends, with no fall-through into the next case. When nothing fits and there's no case _:, the statement does nothing and raises no error. An if/elif ladder without a catch-all fails just as silently.
First match wins, so order is a rule
Both forms stop at the first case that fits, so a broad case placed above a narrow one swallows it. List exact values first, the ranges that contain them after, and the catch-all last.
A teammate moved the retry range to the top. What does this print?
def route(status):
if 400 <= status < 600:
return "retry"
if status == 404:
return "skip"
return "load"
print(route(404))- retry, because 404 falls inside the range tested first and the skip branch never runs.
- skip, because an exact equality test outranks a range test that comes before it.
- retry and then skip, because both conditions are True for a status of 404.
- load, because Python treats 404 as a status that should never be retried.
About this lesson
This section is part of the Control Flow: Intermediate lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.