
On Tue, 1 Jun 2021 at 13:16, Steven D'Aprano <steve@pearwood.info> wrote:
We can distinguish the two contexts by using different signatures. The signature used depends entirely on the call site, not the decorator, so it is easy for the interpreter to deal with.
If the decorator is called on a function or class statement, a single argument is always passed, no exceptions:
# always calls decorate with one argument @decorate def func(): # or class pass
# --> func = decorate(func)
If called on a variable, the number of arguments depends on whether it is a bare name, or a value and annotation are provided. There are exactly four cases:
# bare name @decorate var # --> var = decorate('var')
# name with annotation @decorate var: annot # --> var = decorate('var', annotation=annot)
# name bound to value @decorate var = x # --> var = decorate('var', value=x)
# name with annotation and value @decorate var: annot = x # --> var = decorate('var', annotation=annot, value=x)
Keyword arguments are used because one or both of the value and the annotation may be completely missing. The decorator can either provide default values or collect keyword arguments with `**kwargs`.
I've yet to be convinced that variable annotations are sufficiently useful to be worth all of this complexity (and by "this" I mean any of the proposals being made I'm not singling out Steven's suggestion here). But if we do need this, I quite like the idea of making the distinction based on signature.
The only slightly awkward case is the bare variable case. Most of the time there will be no overlap between the function/class decorators and the bare variable decorator, but in the rare case that we need to use a single function in both cases, we can easily distinguish the two cases:
def mydecorator(arg, **kwargs): if isinstance(arg, str): # must be decorating a variable ... else: # decorating a function or class assert kwarg == {}
So it is easy to handle both uses in a single function, but I emphasise that this would be rare. Normally a single decorator would be used in the function/class case, or the variable case, but not both.
You don't need to do this. Just add another keyword argument "name": # bare name @decorate var # --> var = decorate(name='var') # name with annotation @decorate var: annot # --> var = decorate(name='var', annotation=annot) # name bound to value @decorate var = x # --> var = decorate(name='var', value=x) # name with annotation and value @decorate var: annot = x # --> var = decorate(name='var', annotation=annot, value=x) The single positional argument is reserved for function/class annotations, and will always be None for variable annotations. Paul