On Fri, Nov 20, 2020 at 02:23:45PM +0000, Mark Shannon wrote:

> Why force pattern matching onto library code that was not designed for 
> pattern matching? It seems risky.

Can you give a concrete example of how this will "force" pattern 
matching onto library code? I don't think that anyone has suggested that 
we go around to third-party libraries and insert pattern matching in 
them, so I'm having trouble understanding your fear here.



> Fishing arbitrary attributes out of an object and assuming that the 
> values returned by attribute lookup are equivalent to the internal 
> structure breaks abstraction and data-hiding.

Again, can we have a concrete example of what you fear?

Python is not really big on data-hiding. It's pretty close to impossible 
to hide data in anything written in pure Python.



> An object's API may consist of methods only. Pulling arbitrary 
> attributes out of that object may have all sorts of unintended side-effects.

Well, sure, but merely calling print() on an object might have all sorts 
of unintended side-effects. I think that almost the only operation 
guaranteed to be provably side-effect free in Python is the `is` 
operator. So I'm not sure what you fear here?

If I have a case like:

    match obj:
        case Spam(eggs=x):

I presume any sensible implementation is going to short-cut the 
attempted pattern match for x if obj is *not* an instance of Spam. So 
it's not going to be attempting to pull out arbitrary attributes of 
arbitrary objects, but only specific attributes of Spam objects.

To the degree that your objection here has any validity at all, surely 
it has been true since Python 1.5 or older that we can pull arbitrary 
attributes out of unknown objects? That's what duck-typing does, whether 
you guard it with a LBYL call to hasattr or an EAFP try...except block.

    if hasattr(obj, 'eggs'):
        result = obj.eggs + 1

Not only could obj.eggs have side-effects, but so could the call to 
hasattr. Can you explain how pattern matching is worse than what we 
already do?


> PEP 634 and the DLS paper assert that deconstruction, by accessing 
> attributes of an object, is the opposite of construction.
> This assertion seems false in OOP.


Okay. Does it matter?

Clearly Spam(a=1, b=2) does not necessarily result in an instance with 
attributes a and b. But the pattern `Spam(a=1, b=2)` is intended to be 
equivalent to (roughly):

    if (instance(obj, Spam)
        and getattr(obj, a) == 1
        and getattr(obj, b) == 2)

it doesn't imply that obj was *literally* created by a call to 
the constructor `Spam(a=1, b=2)`, or even that this call would be 
possible.

I think that it will certainly be true that for many objects, there is a 
very close (possibly even exact) correspondence between the constructor 
parameters and the instance attributes, i.e. deconstruction via 
attribute access is the opposite of construction.

But for the exceptions, why does it matter that they are exceptions?

Let me be concrete for the sake of those who may not be following these 
abstract arguments. Suppose I have a class:


    class Car:
        def __init__(self, brand, model):
            self.brand = brand
            self.model = model


and an instance:


    obj = Car("Suzuki", "Swift")


For this class, deconstruction by attribute access is exactly the 
opposite of construction, and I can match any Suzuki like this:


    match obj:
        case Car(brand="Suzuki", model)


which is roughly equivalent to:


    if isinstance(obj, Car) and getattr(obj, "brand") == "Suzuki":
        model = getattr(obj, "model")


It's not actually asserting that the instance *was* constructed with a 
call to `Car(brand="Suzuki", model="Swift")`, only that for the purposes 
of deconstruction it might as well have been.

If the constructor changes, leaving the internal structure the same:


    class Car:
        def __init__(self, manufacturer, variant):
            self.brand = manufacturer
            self.model = variant


the case statement need not change.

Remember that non-underscore attributes are public in Python, so a 
change to the internal structure:


    class Car:
        def __init__(self, brand, model):
            self.brand_name = brand
            self.model_id = model


is already a breaking change, whether we have pattern matching or not.


> When we added the "with" statement, there was no attempt to force 
> existing code to support it. We made the standard library support it, 
> and let the community add support as and when it suited them.
>
> We should do the same with pattern matching.

That's a terrible analogy. Pattern matching is sugar for things that we 
can already do:

- isinstance
- getattr
- sequence unpacking
- equality
- dict key testing

etc. Pattern matching doesn't "force" objects to support anything they 
don't already support.

As far as I can tell, the only thing that the community will need to add 
support for is the mapping between positional attributes and attribute 
names (the `__match_args__` protocol). **Everything** else needed by 
pattern matching is already supported.


[...]
> That `0|1` means something completely different to `0+1` in PEP 634 
> seems like an unnecessary trap.

If so, it's a trap we have managed since the earliest days of Python:


    x in sequence  # It's a bool expression.

    for x in sequence:
    ....^^^^^^^^^^^^^


Syntax that looks exactly like an expression, but means something 
completely different in the context of a for loop.


[...]
> Modifying variables during matching, as you describe, is a serious flaw 
> in PEP 634/642. You don't need a debugger for them to have surprising 
> behavior, failed matches can change global state in an unspecified way.

I think you exaggerate the magnitude of the flaw. We can already write 
conditional code that has side effects, or that changes global state, 
and we could do that even before the walrus operator was introduced.

The PEP doesn't *mandate* that variables are modified during matching, 
it only *allows* it. This is a clear "Quality of Implementation" issue.

Quote:

"""
The implementation may choose to either make persistent bindings for 
those partial matches or not. User code including a match statement 
should not rely on the bindings being made for a failed match, but also 
shouldn't assume that variables are unchanged by a failed match. This 
part of the behavior is left intentionally unspecified so different 
implementations can add optimizations, and to prevent introducing 
semantic restrictions that could limit the extensibility of this 
feature.
"""


I have no problem with this being implementation-defined.




-- 
Steve
_______________________________________________
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/7JBYO5U7MKYWDLU2X6XRFH4PN6M5P3A2/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to