On Thu, May 27, 2021 at 12:33:20PM -0400, Ricky Teachey wrote:

> On Thu, May 27, 2021 at 10:25 AM Steven D'Aprano <[email protected]>
> wrote:
> >
> > Okay. Without reading the source code, does this code snippet use the
> > old `__call__` protocol or the new `__decoration_call__` protocol?
> >
> >
> >     @flambé
> >     class Banana_Surprise:
> >         pass
> >
> 
> Invoking @flambé causes:
> 
> flambé.__decoration_call__(Banana_Surprise, "Banana_Surprise")
> 
> ...to be used, if flambé has that method.

Right: you can't tell from the code you are looking at whether flambé is 
being called as a new or old style decorator. You have to dig into the 
definition of flambé to work out the meaning of the code.

On the one hand we have the current decorator protocol, where the object 
flambé is called with a single argument, the class Banana_Surprise.

I can only think of three cases where something similar occurs, but the 
differences are critical.

- Binary operators can call either or both of two reflected dunders,
  such as `__add__` and `__radd__`.

- Iteration normally calls `__next__`, but it can fall back on 
  the old sequence protocol that relies on `__getitem__`.

- Augmented assignment calls the augmented assignment method, such
  as `__iadd__`, but may fall back on the regular `+` operator.

In all of these cases, the intended semantics are the same and the 
arguments passed are (almost) the same. E.g. normally we would use the 
same signatures for each of the operators:

    def __add__(self, other): ...
    def __radd__(self, other): ...
    def __iadd__(self, other): ... 

and if fact for many purposes, add and radd will be the same function 
object.

The old sequence protocol is a minor exception, because `__getitem__` 
requires an argument (it is called with 0, 1, 2, 3, ... until IndexError 
occurs). But that's almost an implementation detail. The fact that 
`__next__` takes no explicit enumeration, but `__getitem__` does, can be 
ignored when we think about the high-level concept of iteration.

Conceptionally, at a high-level, the sequence and iterator protocols 
operate the same way:

    call the dunder repeatedly until an exception occurs

and the details of which dunder and which exception don't matter. 
Iteration is iteration.

But here, the details *do* matter. Old classic decorators and this 
proposed new decorator syntax do not share a high-level concept. There 
is a certain relationship in the sense that both the new and old 
protocols involve calling a function, but the details are very 
different:

- old decorator protocol is a normal function call with a single 
  argument;

- new protocol uses a different method, with two arguments, 
  one of which is implicit.

Old decorator protocol is about *decorating* a function or class object 
with new functionality (wrapping it in a wrapper function, say, or 
modifying it in place).

New decorator protocol and variable decoration doesn't decorate 
anything: there's nothing to decorate when used with a bare name:

    @decorate
    var

which presumably evaluates to:

    var = decorate.__decorate_call__('var')

So we're not decorating an object. There's no object to decorate!

With a binding:

    @decorate
    var = expression

    var = decorate.__decorate_call__('var', expression)

Again, conceptually we're not decorating anything. We're just reusing 
the `@` syntax for something that is not conceptually related to old 
style decorators. This is just a syntax for *implicitly* getting access 
to the left hand side assignment target name as a string.

[...]
> > I just don't know whether or not the decorator `flambé` receives the
> > name or not.
> >
> 
> If you wrote the decorator, you know.
> 
> If you didn't, why does it matter if it is being passed? Why do you need to
> know that?

Because I don't know the meaning of the code. Am I decorating an object 
or doing something with the target name? I can't interpret the code 
until I know what protocol was supported by the decorator object.

Let me give you an analogy: the `with` statement has a context manager 
protocol that uses an `__enter__` and `__exit__` pair of methods. With 
statements are awesome! They're so awesome that we should use the 
exact same "with" syntax for Pascal-style implicit attribute access, 
using a different protocol.

https://docs.python.org/3/faq/design.html#why-doesn-t-python-have-a-with-statement-for-attribute-assignments


    with myobject:
        do_something()

    with myotherobj:
        do_something()


There is no visible difference between the two pieces of code, 
but one is an old style enter/exit context manager, the other is our 
hypothetical new style with syntax that does something completely 
different and calls different dunder methods.

One of them is calling the global do_something and one is calling the 
object's do_something method. Which is which?

Code that is different should look different.


-- 
Steve
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/[email protected]/message/NOKCTEKOJ7BQIKODKV4TX7W5WUL5P3Y7/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to