On Tue, Jun 23, 2020 at 3:06 PM Emily Bowman email@example.com wrote:
Can you have case (x,x): ? I haven't tried the implementation, but it's not addressed in the PEP that I see, and if that's legal, then _ is effectively just a style choice, rather than a functional one, and there's no reason it shouldn't also be a named match.
Good question. It's explicitly forbidden by the PEP, in the "Name pattern" section:
While matching against each case clause, a name may be bound at most once, having two name patterns with coinciding names is an error. An exception is made for the special single underscore (``_``) name; in patterns, it's a wildcard that *never* binds::
match data: case [x, x]: # Error! ... case [_, _]: print("Some pair") print(_) # Error!
Note: one can still match on a collection with equal items using `guards`_. Also, ``[x, y] | Point(x, y)`` is a legal pattern because the two alternatives are never matched at the same time.
I should add that if you want to check for two values in different positions being equal, you need to use a guard:
match data: case [x, y] if x == y: print("Two equal values")
On Tue, Jun 23, 2020 at 12:11 PM Brett Cannon firstname.lastname@example.org wrote:
I will say that trying to follow https://github.com/python/peps/blob/master/pep-0622.rst#runtime-specificatio... was really hard. Any chance of getting some pseudo-code that shows how a match is performed? Otherwise all of that wording tries so hard to be a spec that I found it hard to follow in my head in how things function.
Sorry about that. This section was subject to heavy editing recently and lost clarity. I will try to make it better! Writing it as pseudo code will take a little time, but I will give it a try.
For instance, "When __match_args__ is missing (as is the default) or None, a single positional sub-pattern is allowed to be passed to the call" is really misleading as it seems that a "sub-pattern" in this case is just going to be a constant like `[1, 2, 3]`. Otherwise how does `["<"|">"]` or `[1, 2, *_]` get represented as a "single positional sub-pattern" (if either of those examples is possible)? The use of the term "sub-pattern" feels misleading because while you may consider even constant patterns a "pattern", going that generic feels like any pattern should fit in that definition when in fact it seems to only be an object where a direct equality check is done.
It seems the way things work is basically:
- `__match__(obj)` returns a proxy object to have Python match against;
it is passed in the thing that `match` is running against, returning `None` if it know there's no chance a match will work 2. If `__match_args__` is present, then it is used to map positional arguments in the pattern to attributes on the proxy object 3. From there the `match` functionality does a bunch of comparisons against attributes on the proxy object to see if the match works
Is that right? That suggests all the work in implementing this for objects is coming up with a way to serialize an object to a proxy that makes pattern matching possible.
Yes, that's right, and the protocol was defined carefully so that the author of __match__ doesn't have to do any pattern matching -- all they have to do is produce an object that has the right attributes, and the interpreter does the rest. Note that it is __match__'s responsibility to check isinstance()! This is because __match__ may not want to use isinstance() but instead check for the presence of certain attributes -- IOW, the class pattern supports duck typing! (This was a little easter egg. :-)
One thing I see mentioned in examples but not in the `__match__` definitions is how mappings work. Are you using `__match_args__` to map keys to attributes? Or are you using `__getitem__` and that just isn't directly mentioned? Otherwise the section on how `__match__` is used only mentioned attributes and never talks about keys.
Oh, __match__ is *only* used for class patterns. Mapping patterns are done differently. They don't use __getitem__ exactly -- the PEP says
Matched key-value pairs must already be present in the mapping, and not created on-the-fly by ``__missing__`` or ``__getitem__``. For example, ``collections.defaultdict`` instances will only match patterns with keys that were already present when the ``match`` block was entered.
You shouldn't try to depend on exactly what methods will be called -- you should just faithfully implement the Mapping protocol.