
Hi Steve, Thank you very much for your comments here. This is certainly not the first time I feel that you not only have an excellent insight into a topic, but also manage to make your points very clearly and succinctly. Your car example highlights the background of the proposed syntax very nicely, indeed, and the for-in example strikes me as quite astute. Kind regards, Tobias Quoting Steven D'Aprano <steve@pearwood.info>:
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-leave@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/7JBYO5U7... of Conduct: http://python.org/psf/codeofconduct/