On Thu, 18 Feb 2021 at 17:35, Brandt Bucher <brandtbuc...@gmail.com> wrote:
>
> Thanks for taking the time to work on this, Mark.

Yes, thanks Mark. I'm not sure I've fully understood the PEP yet but I
can see some parts that I definitely like.

> I fear this is at the expense of most simple classes, which currently "just 
> work" with PEP 634. I'm not convinced that making it easier for very 
> complicated classes (like those in `sympy`) to participate at the expense of 
> everyday classes is a win.

While those sympy classes are quite complicated, the relevant aspect
of them is not. The issue is just that they have positional only
constructor arguments. PEP 634 does not allow for positional only
arguments in a class pattern in a way that can work in general. This
is because it requires attributes to exist for classes that would
otherwise have no need for them.

> For comparison, here is what (as I understand it) each PEP requires for a 
> class to be used in a pattern such as `C(x, y, z)`:
>
> ```py
> class C:
>     """A PEP 634 matchable class."""
>     __match_args__ = ...

You are presuming here that the strings in __match_args__ already
correspond to attributes. Otherwise the PEP 634 version above requires
a property to be added corresponding to each positional argument in
the constructor:

class C:
    """A PEP 634 matchable class"""
    __match_args__ = ['a', 'b', 'c']
    a = property(lambda self: self._rep[0])
    b = property(lambda self: self._rep[1])
    c = property(lambda self: self._rep[2])

At runtime case/match looks into __match_args__ and calls getattr
evaluating each property one by one (even if they all derive from the
same internal data structure).

> class C:
>     """A PEP 653 matchable class."""
>     __match_kind__ = MATCH_CLASS
>     __attributes__ = ...
>     def __deconstruct__(self):
>         ...
> ```

PEP 653 says (although it wasn't immediately obvious to me from reading it):
"""
Classes which define __match_kind__ & MATCH_CLASS to be non-zero must
implement one additional special attribute, and one special method:
"""
Then much later:
"""
object.__match_kind__ will be MATCH_DEFAULT.
"""
I think that means that a simple `class C: pass` will match keyword
patterns with attributes so C(x=1) will match against any C with c.x
== 1.

To match positional arguments both __match_kind__, __attributes__ and
__deconstruct__ would need to be defined but the end result works
better for positional arguments than PEP 634 does:

- It is possible to match the number of positional arguments precisely
so a pattern C(x) would not match an object C(x, y)
- Any invertible mapping between positional arguments and internal
class state can be accommodated.

I don't think it's possible to achieve those with PEP 634.

A concrete example would be matching against a range class:

class range:
    __match_args__ = ['start', 'stop', 'step']
    def __init__(self, start_or_stop, stop=None, step=1):
        if stop is None:
            start = 0
            stop = start_or_stop
        else:
            start = start_or_stop
        self.start = start
        self.stop = stop
        self.step = step

With PEP 634 a pattern like range(10) will also match objects like
range(10, 20) and range(10, 20, 2) etc. I think that is not what
someone familiar with range would expect. With PEP 634 there is no way
to define __match_args__ so that positional argument patterns with
range match in a way that corresponds to its constructor syntax (or
that have useful matching semantics in this case).

I'm not entirely sure but I think that with PEP 653 you can implement this like:

    def __deconstruct__(obj):
        if obj.step != 1:
            return obj.start, obj.stop, obj.step
        elif obj.start != 0:
            return obj.start, obj.stop
        else:
            return obj.stop

I think that would then mean you could use a pattern range(10) to
unambiguously match range(10) without matching range(10, 20) etc.

Above I suggested that any *invertible* mapping of args to state could
be supported and here there is an example of a non-invertible mapping
since range(0, 10) is the same as range(10). I might have
misunderstood but I don't think that PEP 653 makes it possible to have
a pattern range(10) and a pattern range(0, 10) both match against the
same object range(10). This means that the pattern would always need
to use the "canonical" form of the args (e.g, range(10) in this case).

--
Oscar
_______________________________________________
Python-Dev mailing list -- python-dev@python.org
To unsubscribe send an email to python-dev-le...@python.org
https://mail.python.org/mailman3/lists/python-dev.python.org/
Message archived at 
https://mail.python.org/archives/list/python-dev@python.org/message/EXJ6THG6WK2RRXFLCRSY7MRBBKOSRYID/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to