We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Modern Syntax and Modernisation step 15 of 18
Sequence and mapping patterns: the two rules everyone gets wrong
Two rules about structural patterns cause almost all of the confusion, and one of them is a security-relevant misconception.
Rule 1 — str and bytes are not sequences here
match payload:
case [a, b, c]:
...
Given payload = "abc" this does not match. str, bytes and bytearray
are explicitly excluded from sequence patterns (PEP 634), precisely because
“iterate a string and you get characters” is the source of a thousand bugs. You
do not have to write a defensive case str(): ... first — although you may want
one anyway, to route strings deliberately.
Anything else registered as a collections.abc.Sequence does match: list,
tuple, range, memoryview, and your own classes if you register them.
Rule 2 — mapping patterns are partial, and there is no way to make them total
match payload:
case {"type": "user"}:
create_user(payload)
This matches {"type": "user"}. It also matches
{"type": "user", "is_admin": True, "id": 0, ...forty more keys}. Extra keys
are ignored, always, and the language provides no syntax to demand an exact
key set. case {}: does not mean “empty mapping” — it means “any mapping at
all”, the mapping equivalent of _.
The consequence is worth saying plainly: match is not schema validation.
It is a dispatch mechanism. If your requirement is “reject payloads with
unexpected keys”, you need a validator — a TypedDict with closed=True
(3.15), a pydantic model with extra="forbid", or an explicit key-set check.
Reaching for match there gives you a shape check that silently admits
everything you were trying to exclude.
A **rest capture collects the unmatched keys (case {"type": t, **rest}:),
which lets you inspect the extras — but a bare **_ is a SyntaxError, and
capturing rest still does not make the pattern refuse them.
What you are building
def describe(cmd: object) -> str
def solve(commands: list[object]) -> list[str]
describe classifies one command; solve maps it over the list. In order:
| pattern | result |
|---|---|
| empty sequence |
"empty" |
one-element sequence whose element is a str v |
"verb:{v}" |
sequence whose first element is a str v, with n more |
"verb:{v}+{n}" |
any other sequence of length n |
"seq:{n}" |
a str s |
"text:{s}" |
bytes or bytearray |
"binary" |
mapping with "type": "batch" and a sequence "items" of length n |
"batch:{n}" |
mapping with "type": "user" and an int "id" u |
"user:{u}" |
mapping with a str "type" k |
"kind:{k}" |
| any other mapping |
"mapping" |
| anything else |
"scalar" |
Write the sequence cases before the str and bytes cases. The tests
include a three-character string precisely so that a solution which “defends”
by ordering the str case first learns nothing — and one which relies on the
exclusion rule passes.
Related
The exactness that mapping patterns cannot express is exactly what a closed
TypedDict gives you, and what extra="forbid" gives you in a validation
library. Pattern matching and validation are different jobs; use both.
Stuck?
Python reference solution
Sign in to attempt this problem and reveal the reference solution.