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 kindmatch-case (3.10+)Python 3.9 equivalentMatches 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))

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.