skip to content

In a Python match statement, what does the mapping pattern `case {"kind": k}` require of the subject?

level: middleimportance: should knowfreq 38%

answer

  1. The pattern need not be complete
  2. Extra keys cost nothing at all
  3. Insertion order never enters into it
  4. Ask what an empty brace pattern matches

basics

~20 s

Only that the subject is a mapping which contains the key "kind"; its value binds to k. Extra keys are ignored and key order is irrelevant, so case {} matches any mapping at all rather than only an empty one.

solid answer

~40 s

A mapping pattern is a subset test, not an equality test. The subject must be an instance of `collections.abc.Mapping` - `dict`, `collections.ChainMap`, `collections.Counter`, `types.MappingProxyType` and friends qualify - and it must contain every key the pattern lists; any additional keys are simply ignored, and order never enters into it. That is why `case {}` matches every mapping, which surprises people who read it as "the empty dict". Values are fetched with the two-argument form of the mapping's `get()`, so a `collections.defaultdict` factory is not fired and no key is inserted by the attempt. Add `**rest` to capture the unmatched pairs as a new `dict`; `**_` is a `SyntaxError`, as is a duplicate literal key or a bare name used as a key.

code

python · 12 lines
python
def route(event):
    match event:
        case {"kind": "digest", "recipient": to, **rest}:
            return f"digest -> {to}, {len(rest)} extra keys"
        case {"kind": kind}:
            return f"ignored kind {kind!r}"
        case _:
            return "not a mapping"

print(route({"recipient": "[email protected]", "kind": "digest", "attempt": 17}))
print(route({"kind": "ping"}))
print(route([("kind", "digest")]))

go deeper

for a junior

Recall the headline: a mapping pattern checks only the keys it names, so extra keys are fine and order is irrelevant. Know that the value of each named key binds to the name written beside it.

for a middle

Explain the mechanics: the collections.abc.Mapping type test, why case {} matches everything, what **rest produces, and which spellings are compile-time errors - **_, a bare name as a key, duplicate literal keys.

for a senior

Demonstrate the ordering judgement subset semantics forces: a loose pattern placed first quietly claims payloads a precise one was written for, and no warning is issued. Talk about keeping dispatch ladders specific-to-general as producers add fields.

for a principal

Own the contract question behind it - whether payload shape should be validated at the boundary rather than discovered in a dispatch ladder, and how far structural matching should carry a system before the shapes deserve declared types.

Mapping patterns are the brace-delimited form on the left of a `case`, and their defining property is that they are **partial**. A sequence pattern pins down the whole shape - the exact arity, or a minimum with a star. A mapping pattern pins down only the keys you name and says nothing about the rest, because dictionaries in real programs grow fields over time and a pattern that broke whenever a producer added a key would be useless. ## The test 1. First a **type test**: the subject must be an instance of `collections.abc.Mapping`. `dict` satisfies that, and so do `collections.OrderedDict`, `collections.defaultdict`, `collections.Counter`, `collections.ChainMap`, `types.MappingProxyType` and `os.environ`. A list of key-value pairs does not; neither does an arbitrary object with a `get` method, because the check is on the type, not on the methods present. 2. Then, for each key in the pattern, the value is looked up and matched against the corresponding sub-pattern. The lookup goes through the two-argument form of the mapping's `get()` with an internal sentinel, which has a pleasant consequence: matching a `collections.defaultdict` against a key it lacks does not fire the default factory and does not insert anything. **A pattern never mutates its subject.** ## Subset semantics, stated plainly `case {"kind": "digest", "recipient": to}` matches a mapping that has both keys with a matching `kind` value, no matter what else it carries and no matter what order the keys were inserted in. Two corollaries follow that are worth saying out loud in an interview: 1. First, `case {}` matches *any* mapping - it demands a subset of zero keys - so a bare brace pattern in a ladder acts as a mapping-shaped catch-all and shadows every later mapping case. 2. Second, ladders must run **specific to general** here just as they do for sequences: a case naming one key will happily swallow subjects that a later, three-key case was written for. ## Capturing the remainder `**rest` at the end of a mapping pattern binds a new `dict` containing exactly the key-value pairs the pattern did not name. It is a fresh dictionary, not a view, so mutating it does not touch the subject. Two spellings are rejected: - `**_` is a `SyntaxError` because discarding the remainder is what a mapping pattern already does by default, so writing it can only be a mistake; - and a mapping pattern accepts at most one double-star, always last. ## What may appear as a key Keys are not patterns in the general sense - they must be **literals or dotted value names**. - `case {"kind": k}` is fine, and so is a key written as a dotted constant such as an enum member. - A bare name as a key is a `SyntaxError`, because a capture pattern there would have to mean "any key", which is not a thing a subset test can express. - Duplicated literal keys are also rejected at compile time with a message naming the offending key; duplicates that only collide after evaluating dotted names raise `ValueError` when the case is attempted. ## Where the ordering assumption bites Consider a dispatcher for outbound notifications, keyed on the payloads a queue hands it. A developer who thinks of a mapping pattern as "destructuring in order" writes their cases as though the producer's key order mattered, or as though a payload with extra bookkeeping fields would fail to match. Neither is true, and the real hazard runs the other way: their first, loosest case - one key, no guard - silently claims payloads that the later, precise case was built for, and the wrong branch runs against seventeen upstream producers that each add their own fields. The fix is ordering, not more keys: - put the most specific pattern first, - and if two patterns can both match the same payload, make the discrimination explicit in the pattern rather than trusting arrival order. ## Contrast with a sequence pattern Sequence patterns are **total and positional**; mapping patterns are **partial and keyed**. Both share the same two-stage structure - type test, then structure test, then bind - and both refuse to duck-type their way in. Holding those two sentences together is usually enough to answer anything asked about either.

  • What does `case {}` match in a match statement?
    Every mapping, not only an empty one. A mapping pattern requires the subject to contain the keys it names, and this one names none, so any `dict` or other `collections.abc.Mapping` instance satisfies it. Placed early in a ladder it shadows every later mapping case; if you truly want the empty dict, test the length in a guard or compare against `{}` explicitly.
  • Why is `**_` rejected in a mapping pattern?
    It is a `SyntaxError`. Ignoring the unnamed keys is already the default behaviour of every mapping pattern, so `**_` would add nothing and can only reflect a misunderstanding of subset semantics. Use `**rest` when you actually want the leftover pairs as a new `dict`, and write nothing at all when you do not.
  • Does matching a `collections.defaultdict` against a missing key insert that key?
    No. Mapping patterns fetch values through the two-argument form of the mapping's `get()`, which does not invoke `__missing__` and therefore never fires the default factory. The subject is left exactly as it was - matching is a read-only operation, which matters when the same payload is matched by several dispatchers in turn.

saying these in an interview costs you the question

  • Thinks the pattern must list every key in the subject
  • Reads `case {}` as matching only the empty dict
  • Believes key order in the subject affects the match
  • Says `**rest` gives a view onto the original mapping
  • Uses a bare name as a key expecting it to capture any key

context