Search code examples
pythonmatchstructural-pattern-matching

Structural pattern matching binds already defined variables but treats instance attributes as literals: is this documented anywhere?


PEP 636 - Structural Pattern Matching discusses how pattern matching binds values to variables declared on the go:

match command.split():
    case ["quit"]:
        print("Goodbye!")
        quit_game()
    case ["look"]:
        current_room.describe()
    case ["get", obj]:
        character.get(obj, current_room)

A pattern like ["get", obj] will match only 2-element sequences that have a first element equal to "get". It will also bind obj = subject[1].

If obj already had a value, we might expect the value to be used as if it was a literal, but that doesn't happen. Instead obj is overwritten:

>>> # I'm using Python 3.12. You can copy/paste this straight into iPython
>>> obj = 3
>>> for i in range(5):
...     match i:
...         case obj:
...             print(i)  # Does this print only 3?
0
1
2
3
4
>>> obj
4

This doesn't happen if you specify an instance attribute:

>>> from dataclasses import dataclass
>>> @dataclass
... class Num:
...     x: int
...
>>> num = Num(x=3)
>>> for i in range(5):
...     match i:
...         case num.x:  # does this bind num.x to i???
...             print(i)
...
3

In this second case, num.x is unchanged and is used as if it were a literal.

This isn't mentioned anywhere in PEP 636 that I can see. Where can I read about this subtlety? Was this behaviour introduced after PEP 636 was implemented?

(Note: I think this behaviour is perfectly sensible and good. I'd just like to know if/where it's documented)


Solution

  • This is specified in the Capture Patterns section of the documentation of match:

    capture_pattern ::=  !'_' NAME
    

    NAME is just a single identifier, so num.x is not included in this syntax. !'_' excludes the special case of _, which is defined as a wildcard pattern in the next section.