5

I'm trying to run an example from the docs, but get the error:

Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: Point() accepts 0 positional sub-patterns (2 given)

Can someone explain what I doing wrong here?

class Point():
    def __init__(self, x, y):
            self.x = x
            self.y = y

x, y = 5 ,5
point = Point(x, y)
match point:
    case Point(x, y) if x == y:
        print(f"Y=X at {x}")
    case Point(x, y):
        print(f"Not on the diagonal")
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
HelgiBergmann
  • 73
  • 1
  • 6

1 Answers1

9

You need to define __match_args__ in your class. As pointed at in this section of the "What's new in 3.10" page:

You can use positional parameters with some builtin classes that provide an ordering for their attributes (e.g. dataclasses). You can also define a specific position for attributes in patterns by setting the __match_args__ special attribute in your classes. If it’s set to (“x”, “y”), the following patterns are all equivalent (and all bind the y attribute to the var variable):

Point(1, var) 
Point(1, y=var) 
Point(x=1, y=var) 
Point(y=var, x=1)

So your class will need to look as follows:

class Point:                                                                                            
    __match_args__ = ("x", "y")                                                                         
    def __init__(self, x, y):                                                                           
        self.x = x                                                                                      
        self.y = y                                                                                      

Alternatively, you could change your match structure to the following:

match point:                                                                                            
    case Point(x=x, y=y) if x == y:                                                                       
        print(f"Y=X at {x}")                                                                            
    case Point(x=x, y=y):                                                                                   
        print(f"Not on the diagonal")                                                                   

(Note that you don't need a both: a class with __match_args__ defined, does not need to have its arguments specified in the match-case statements.)

For full details, I'll refer readers to PEP 634, which is the specification for structural pattern matching. The details on this particular point are in the section Class Patterns.

For a better introduction or tutorial, don't use the "What's New" documentation, as it tends to provide an overview, but may skip over some things. Instead, use PEP 636 -- Structural Pattern Matching: Tutorial, or the language reference on match statements for more details.


It is mentioned in the quoted text that a dataclass will already have an ordering, and in your example, a dataclass also works fine:

from dataclasses import dataclass                                                                       
                                                                                                        
@dataclass                                                                                              
class Point:                                                                                            
    x: int                                                                                              
    y: int                                                                                              
                                                                                                        
x, y = 5, 5                                                                                             
point = Point(x, y)                                                                                     
                                                                                                        
match point:                                                                                            
    case Point(x, y) if x == y:                                                                         
        print(f"Y=X at {x}")                                                                            
    case Point(x, y):                                                                                   
        print(f"Not on the diagonal")                                                                   
9769953
  • 10,344
  • 3
  • 26
  • 37
  • Good find. The documentation should have included a better example for `Point` when position-matching params. In effect, a copy-paste from the examples for `__match_args__` would fail, like it has for the OP. – aneroid Oct 19 '21 at 09:09
  • @aneroid True, but I think better documentation right now is to be found in the [PEP 636 -- Structural Pattern Matching: Tutorial](https://www.python.org/dev/peps/pep-0636/), or the language reference on [match statements](https://docs.python.org/3/reference/compound_stmts.html#the-match-statement). The "What's New" tends to provide just a quick overview, so it will skip some things. – 9769953 Oct 19 '21 at 09:18
  • Fair enough. But I think that's confusing for people referring to the docs, because now they'd have to read the PEP and track it against which Python version a feature belongs to, if it changes in the future. Better for the docs to have just a one-liner and then "go read PEP 636". So the relevant info is one place. Your answer is still the right one, of course :-) – aneroid Oct 19 '21 at 09:37