
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.
I'm not the OP, but the way I understand the proposal __decoration_call__ is only invoked when you actually *use an object to decorate something*. That means that a decorator factory will just invoke __call__ as normal, because it's nothing but a convenient way to generate a decorator. It is not itself a decorator, nor is it used to actually decorate anything. To illustrate this point we can separate it out across several lines: @factory("foo") def bar(): pass Can be rewritten as: decorator = factory("foo") @decorator def bar(): pass So __decorator_call__ will only be invoked on the object that gets returned from `factory("foo")`, not on `factory`. 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.
The OP mentioned a default implementation for __decoration_call__ of: def __decoration_call__(self, func, by_name): if func is None: return self(by_name) return self(func) Such that you can assume that the decorator will *always* receive the name, but may choose to discard it and not make use of it if it doesn't implement the __decoration_call__ interface and instead opts to use default implementation which falls back on __call__. For decorated functions the name can always be pulled out of the function object as normal even when using __call__, but to make use of the name in a decorated assignment statement the decorator would have to override __decoration_call__. At this point I will say that I may be putting words into OPs mouth, and would be happy to be corrected if I've misunderstood. One final point I've just thought of is that Ricky suggested that when no value is assigned to a name that the object reference be `None`. But I don't think that works, because it becomes indistinguishable from when `None` is explicitly assigned. We would need some sentinel value instead of `None` to remove ambiguity in this situation: 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 Thu, May 27, 2021 at 3:25 PM Steven D'Aprano <steve@pearwood.info> wrote:
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/GZQ2RT... Code of Conduct: http://python.org/psf/codeofconduct/