We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modern Syntax and Modernisation step 11 of 18
match/case fundamentals: literal, capture, wildcard
match looks like a switch statement and is not one. The gap between those two
mental models produces one specific, silent, expensive bug:
OK = 200
NOT_FOUND = 404
def classify(status: int) -> str:
match status:
case OK: # <-- NOT a comparison
return "ok"
case NOT_FOUND: # <-- dead code, forever
return "missing"
case OK: is a capture pattern. It matches anything, binds the subject
to the name OK — shadowing the module constant for the rest of the function —
and returns "ok". Every later case is unreachable. There is no exception, no
warning, and every test that happens to pass a 200 goes green.
The rules that actually matter
match and case are soft keywords. match = re.match(...) still works;
the parser decides from context. That is why the language could not simply
reserve case NAME as a comparison.
A bare name is always a capture. To compare against a constant the pattern
must contain a dot — case Code.OK:, case http.NOT_FOUND:. This is a
deliberate design decision (PEP 634 §”Value patterns”): the alternative, making
capture require a sigil, would have made the common case noisy. The practical
consequence is that enums are the natural constant vocabulary for match,
because their members are always dotted.
Literal patterns use ==, except True/False/None, which use is.
So case True: does not match 1, but case 1: does match True, because
1 == True. Order the blocks accordingly.
At most one irrefutable pattern, and it must be last. A bare capture or _
after another case is a SyntaxError — but only when the compiler can see it
is irrefutable. case x if x > 3: is refutable (the guard can fail) and may
appear anywhere.
What you are building
def classify(status: int | str) -> str
def solve(statuses: list[int | str]) -> list[str]
solve maps classify over the list. classify returns:
| subject | result |
|---|---|
True / False |
"enabled" / "disabled" |
1 / 0 |
"one" / "zero" |
Code.OK (200) |
"ok" |
Code.NOT_FOUND (404) |
"missing" |
Code.TEAPOT (418) |
"teapot" |
204 |
"no-content" |
any other int >= 500 |
"server-error" |
"retry" |
"retry" |
any other str s |
"text:{s}" |
| anything else |
"other" |
The Code enum is given. The test data is chosen so that a solution using bare
names instead of dotted value patterns gets the first case right and every
subsequent case wrong — which is exactly how this bug reaches production.
The static defence
mypy’s --warn-unreachable reports the dead branches a stray capture creates:
Statement is unreachable. It is not part of --strict (strict is thirteen
flags and this is not one of them), but it is part of this course’s Silver
tier, and it is the cheapest possible insurance against the trap above. Turn it
on in any module that uses match.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.