[Python-ideas] Proposal for new-style decorators
Christophe Schlick
cschlick at gmail.com
Wed Apr 27 03:42:22 CEST 2011
On Tue, Apr 26, 2011 at 11:20 PM, Terry Reedy <tjreedy at udel.edu> wrote:
>
> There is no special syntax for defining decorators -- just normal nested
> function or class definition syntax. To put it another way, Python does not
> have decorator objects. A decorator is simply a callable (function, class,
> or class instance with __call__ method) applied to a object
I totally agree with that. When defining a decorating function, you
don't have any syntactic element that could explain the reader of your
code that this function is actually a decorator. It is only when
applied on a function with the @-syntax that the mechanism becomes
visible (but this is not always done in the same file). This can be
considered as a strength (any function with a correct input/output is
likely to later become a decorator, even if the original author did
not thought about it). However, according to my own little experience,
more than 9 times out of 10, you perfectly now when writting such a
function that it is actually a decorator. Using the proposed
@decorator idiom (hey see, I haven't written "syntax" ;-) has at least
the advantage to be explicit when you want to be explicit (besides the
other features it provides),
> What you are actually proposing is a meta-decorator (class) whose instances
> can be used as decorators because the class has a __call__ instance method.
> This sort of thing is a known alternative to the nested function pattern.
Yes, I know that using callable class can be an alternative to the
nested function pattern. In the pattern you talk about, the __init__
method gets the decorator arguments, the __call__ method serves as a
decorator making function, and a third method is used to generate the
actual decorating function. As a result, the boilerplate is
approximatively the same as with the nested functions idiom. But this
is not what is done here, because the end-user only writes a single
function, not a whole class.
In my proposal, the two nested functions are avoided by the fact that
(1) the decorator attributes are automatically injected as
meta-attributes (this is the role of the middle nested function in the
standard idiom), and (2) the decorating function is in charge to pass
the whole set of arguments to the undecorated function (this is the
role of the inner nested function in the standard idiom). As far as I
know, I haven't seen the combination of these two elements before.
> Stick with the two real problems.
>
> 1. The double or triple nested function pattern has a lot of boilerplate and
> can be difficult to learn. Hiding boilerplate in a class makes the use
> pattern simpler and easier to learn. This is a real benefit.
> 2. Introspection (more comments below), which your class also addresses.
OK. I'll drop the arguments on nested function, and simply focus on
boilerplate and introspection. That makes sense.
>> def old_style_repeat_var(n=3, trace=True):
>> """docstring for decorating function"""
>
> Actually, this is the docstring for the decorator making function.
Also agree. I've written "decorating function" by symmetry with the
new idiom, but I knew that I would get some remark here ;-)
>> '@wraps(func)' copies the name and the docstring from the undecorated
>> function to the decorated one, in order to get some useful piece of
>> information when using 'help'. However, the signature of the function
>> still comes from the decorated function, not the genuine one.
>
> I am not sure what you mean. If the two signatures are different, then one
> must use the signature of the wrapper when calling it, not the signature of
> the wrappee, which is perhaps what you mean by 'the genuine
> one'. The problem of generic wrappers having generic signatures (*args,
> **kwds) is endemic to using generic wrappers instead of special case
> wrappers.
What I wanted to say is that wraps only does half of the job: it
correctly copies the name and the docstring, but the signature
presented by the help function is still test(*args, **keys), while it
should actually be test(first=0, last=0) according to the undecorated
function.
The alternative proposed by the new idiom is to copy nothing at all:
it simply says "OK, 'test' is a decorated function. If you want to
know more look at 'test.func' to get the info about the undecorated
one, and at 'test.deco' to see what the decorator has done". I prefer
such a raw-but-explicit approach rather than an automatic half-baked,
half-bogus one. Moreover, it is not easy with the 'functools' module
to provide introspection of the decorating function, once you've got
the decorated one.
>> The only
>> solution is to inspect the undecorated function and then use 'exec' to
>> generate a wrapper with a correct signature. This is basically what is
>> done in the 'decorator' package (available at PyPI) written by Michele
>> Simionato. There has been a lengthy discussion in python-dev (in 2009
>> I guess, but I can't find the archive right now) whether to include or
>> not this package in the standard library.
>
> The other solution is to not use a generic wrappers with generic signatures
> but to write specific wrappers with the actual signature, which people did,
> for instance, before functools and partial() were added to Python.
Right again, but this overweights the boilerplate even more compared
to the '@wraps' decorator, no?
> I second the other recommendations to make your proposal
> available on the cookbook site, etc.
That sounds good... Thanks a lot, Terry!
More information about the Python-ideas
mailing list