What happens if the decorator factory has `__decoration_call__` and the
object it returns only has `__call__`? I presume you get this:
func = decorator.__decoration_call__("spam this", "func").__call__(func)
And let's not forget the other two combinations:
func = decorator.__decoration_call__("spam this", "func").__decoration_call__(func, "func")
func = decorator.__call__("spam this").__call__(func)
The last one is, of course, the current behaviour for a decorator
factory.
The bottom line here is that is you have plain, unadorned decorator:
@decorator
there are two possible behaviours and no obvious way to tell which one
is used, short of digging into the implementation. But if you have a
decorarator factory:
@factory(*args, **kwargs)
there are now four possible behaviours. And anyone brave enough to use a
double-barrelled factory-factory
@factory(*args, **kwargs)(*more_args)will be faced with eight possible combinations.
@factory("foo")
def bar():
pass
decorator = factory("foo")
@decorator
def bar():
pass
It seems to me that this proposal means that we can't even tell which of
the two protocols (classic decoration, or new `__decoration_call__`
style decoration) without digging into the implementation of the
decorator.
To be precise, the problem here as reader isn't so much the fact that I
don't know whether the object is called using the `__call__` protocol or
the new-style `__decorator_call__` protocol, but the fact that I can't
tell whether the calls will involve the name being passed or not.
def __decoration_call__(self, func, by_name):
if func is None:
return self(by_name)
return self(func)
from somewhere import NOTSET
@decorate
foo: int
def __decoration_call__(self, obj, names, annotation):
print(obj is None) # False
print(obj is NOTSET) # True
@decorate
foo: int = None
def __decoration_call__(self, obj, names, annotation):
print(obj is None) # True
print(obj is NOTSET) # False
On Wed, May 26, 2021 at 12:43:48PM -0400, Ricky Teachey wrote:
[...]
> These two ideas of a decorator syntax result are not the same:
>
> RESULT A: function decorator
> # func = decorator("spam")(func)
>
> RESULT B: variable decorator
> # name = decorator("spam")("name")
>
> ...because func is passed as an object, but "name" a string representing
> the name of the object. Two very different things.
Ricky, it's not clear to me whether you are proposing the above RESULT A
and RESULT B as an *alternative* to the "variable decorator" proposal,
or if you have just misunderstood it. The current variable decorator
proposal on the table is for this:
@decorator(spam) name
# --> name = decorator("name", spam)
rather than what you wrote:
# name = decorator("spam")("name")
So I can't tell whether the difference between your version and the OPs
is a bug or a feature :-)
> For this reason I think I would agree even more so that the differences in
> the decorator behavior would be an extremely significant point of confusion.
[...]
> Maybe employment of decorator syntax could OPTIONALLY trigger a new dunder
> method-- here I'll just call it __decoration_call__-- with the signature:
>
> def __decoration_call__(self, obj: Any, by_name: str) -> Any: ...
To be clear here, I think that your proposal is that this method is to
be looked up on the *decorator*, not the thing being decorated. Is that
correct?
In other words:
@decorator
class X: ... # or a function, or something else
it is *decorator*, not X, that is checked for a `__decoration_call__`
method.
Correct?
> My idea is to optionally allow any callable object to write a
> __decoration_call__ method that gets called in lieu of the __call__ method
> when the callable object is employed using decorator syntax. When this
> happens, the decorated named is supplied- not counting self- as the first
> argument (e.g., by_name), which contains the str value of the name the
> decorator was applied to.
In current Python, the only objects which can be decorated with the @
syntax are callable functions and classes. So it is ambiguous to talk
about "any callable object" without stating whether it is the decorator
or the thing being decorated.
> In actuality, unless I'm wrong (I might be; not an expert) current
> decorator syntax is really sugar for:
>
> def func(): ...
> func = decorator.__call__("spam this").__call__(func)
Roughly speaking, that would correspond to
@decorator("spam this")
def func():
...
If we have a bare decorator, we have this:
@decorator
def func():
...
# --> func = decorator.__call__(func)
> My proposal is to make it such that:
>
> @decorator
> def func(): ...
>
> ...*can result* in this:
>
> def func(): ...
> func = decorator.__decoration_call__( func, "func")
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
It seems to me that this proposal means that we can't even tell which of
the two protocols (classic decoration, or new `__decoration_call__`
style decoration) without digging into the implementation of the
decorator.
To be precise, the problem here as reader isn't so much the fact that I
don't know whether the object is called using the `__call__` protocol or
the new-style `__decorator_call__` protocol, but the fact that I can't
tell whether the calls will involve the name being passed or not.
This is because the name is being *implicitly* passed, in a way that is
unclear whether or not it will be passed.
I just don't know whether or not the decorator `flambé` receives the
name or not.
> And also so that this:
>
> @decorator("spam this")
> def func(): ...
>
> ...*can result* in this:
>
> def func(): ...
> func = decorator.__call__("spam this").__decoration_call__(func, "func")
What happens if the decorator factory has `__decoration_call__` and the
object it returns only has `__call__`? I presume you get this:
func = decorator.__decoration_call__("spam this", "func").__call__(func)
And let's not forget the other two combinations:
func = decorator.__decoration_call__("spam this", "func").__decoration_call__(func, "func")
func = decorator.__call__("spam this").__call__(func)
The last one is, of course, the current behaviour for a decorator
factory.
The bottom line here is that is you have plain, unadorned decorator:
@decorator
there are two possible behaviours and no obvious way to tell which one
is used, short of digging into the implementation. But if you have a
decorarator factory:
@factory(*args, **kwargs)
there are now four possible behaviours. And anyone brave enough to use a
double-barrelled factory-factory
@factory(*args, **kwargs)(*more_args)
will be faced with eight possible combinations.
And at this point, I'm afraid I have run out of steam to respond further
to this proposal. Sorry.
--
Steve
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-leave@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at https://mail.python.org/archives/list/python-ideas@python.org/message/GZQ2RTWVNW6KC2REISB34TLRH45ZJIMK/
Code of Conduct: http://python.org/psf/codeofconduct/