[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