On Thu, May 27, 2021 at 11:09 AM Matt del Valle <matthewgdv@gmail.com> wrote:

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`.

Correct.
 
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__.

Yes but I am on the fence as to whether this default implementation (I suppose it would live on the object class?) should be considered or not. It would certainly provide a lot of functionality "out-of-the-box".

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.

Nah you got it.
 
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


Bikesheddable, but I don't know why having these two be equivalent:

@decorator var
@decorator var = None  

..would be a problem. Having an implied default of None for var above makes sense to my brain. Do you have an example in mind where you think it would create a problem?


---
Ricky.

"I've never met a Kentucky man who wasn't either thinking about going home or actually going home." - Happy Chandler