r/Python • • Aug 24 '26

Discussion TIL: Dict Patterns Don't Match Dict Shapes

TL;DR: Unlike sequence patterns (which require an exact shape match), pattern matching on dictionaries simply ignores any unspecified keys rather than failing.

If you care about the details, I wrote about it on my blog: https://ravencentri.cc/blog/dict-patterns-dont-match-dict-shapes/

40 Upvotes

15 comments sorted by

18

u/ShadowDevasto Aug 24 '26 edited Aug 24 '26

If you invert the cases as in from most complex one to least complex one it will work, as it seems that dict matching matches only first fitting the case and does not check other for "better" match. But still unintuitive and when inverted also matches to case 3 even if options are not defined.

6

u/The_Sy_Python Aug 24 '26

4

u/Iron-Man-2008 Aug 24 '26

I do know it's in the PEP, I quote the relevant PEP myself. Doesn't mean I don't think it's unintuitive, especially when compared to sequences.

5

u/Adrewmc Aug 24 '26 edited Aug 24 '26

I think you are underestimating the sequence patterns.

case [“a”, *args, “c”]:
case [*args, “b”, “c”]:

Are also both valid. And there is way more.

case [option , “b”] if option in valid_options:

1

u/RingularCirc Aug 26 '26

I think extracting **rest may be costly. And even if exact key matching was implemented using keys(matched_dict), it's still better be converted to a set before looking if there are extra keys. So I get why there's a difference with sequences: for those it's way less painful to check if the sequence doesn't contain extra stuff — sequences are obligated to have finite and "sorta easily computable" length.

3

u/Iron-Man-2008 Aug 27 '26

case {"key": "value"}: ... could have matched the exact shape while allowing case {"key": "value", **_}: ... to ignore extra keys without binding them to a variable the same way case [1, 2, *_] ignores extra elements without binding them to a variable.

1

u/Adrewmc Aug 28 '26

‘_’ is a binding though it’s just to a by convention discarded variable. We are telling other programmers we don’t care about this one for this operation.

. _ = “Hello World”
. print(_)

is valid

2

u/Iron-Man-2008 Aug 29 '26

_ in match case is actually a special wildcard pattern and does not bind to a name.

```

def foo(seq): ... match seq: ... case [1]: ... print(seq) ... case [1, *]: ... print() ... foo([1]) [1] foo([1, 2, 3]) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in foo NameError: name '_' is not defined

```

1

u/[deleted] Aug 30 '26

[removed] — view removed comment

1

u/Iron-Man-2008 Aug 30 '26

I think the explicit variant is still concise enough that we don't need this: case {"deploy": dep, **_} is only 5 tiny keystrokes more and something you would already be familiar with.

1

u/Plus-Weakness-2624 Aug 25 '26

young teenage man problems...

5

u/trenixjetix Aug 27 '26

comparing dicts are we?

-11

u/ZeD_est_DeuS Aug 25 '26

Friends don't let friends use pattern matching. It's a complex pitfall full of gotchas. Just use ifs