[Python-ideas] Proposal for new-style decorators

Terry Reedy tjreedy at udel.edu
Tue Apr 26 23:20:38 CEST 2011


On 4/26/2011 11:05 AM, Christophe Schlick wrote:

I got interrupted in responding this, and you have now posted parts 2 
and 3, so I will snip and revise a bit.

 > a new syntax for defining decorators in Python.

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 
(function or class) with the @deco syntax (before the def/class 
statement) instead of the normal call syntax (after the def/class 
statement). Decorators return either the original object (usually 
modified) or another object that is usually, but not necessarily, of the 
same kind as the input.

As Stephen noted, syntax is what is defined in the Language reference. 
Code patterns are what are embodied in the stdlib (or pypi or the Python 
cookbook or other example repositories).

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.

> programmer has no other choice than to create a dummy function name

Many of us consider dummy names a bogus problem. I recommend you skip 
this argument. In any case, this, like the other 'problems' you describe 
for nested functions, has nothing in particular with their usage as 
decorators.

> (only used for one single 'return' statement), which is never a good
> coding principle, whatever the programming language.

This strikes me as a bogus rule: a single use of a local name is quite 
common, and not just in Python. I recommend leaving this also out of 
your problem list. 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. One of 
the three major benefits of writing a generator function versus an 
equivalent iterator class is that is hides the boilerplate required for 
the latter. Similarly, for loops hide the boilerplate required for an 
equivalent while loop.

2. Introspection (more comments below), which your class also addresses.

> #---
>    def old_style_repeat_var(n=3, trace=True):
>      """docstring for decorating function"""

Actually, this is the docstring for the decorator making function.

>      def dummy_deco_name_never_used(func):
>      """docstring never used"""
>        # @wraps(func)
>        def dummy_func_name_never_used(*args, **keys):
>          """docstring for decorated function"""
>          if trace:
>            print "apply 'old_style_repeat_var' on %r" % func.__name__
>          for loop in range(n): func(*args, **keys)
>        return dummy_func_name_never_used
>      return dummy_deco_name_never_used
> #---

> This time a two-level function nesting is required and the code needs
> two dummy names for these two nested functions.

'deco' and 'wrapper' work for me. But I agree that this is a bit 
confusing. But I think that is somewhat inherent in calling a 
decorator-maker f1 to return decorator f2 that returns wrapper f3 that 
wraps the original function f.

 > Note that the  docstring of the middle nested function
 > is even totally invisible for introspection tools.

Not true. To see the docstring of a dynamically created temporary 
function, you have to either dynamically create it or dig inside the 
function that creates it to find the constant string:

 >>> old_style_repeat_var().__doc__
'docstring never used'

 >>> old_style_repeat_var.__code__.co_consts[1].co_consts[0]
'docstring never used'

But I am not sure why you would want to see it or even have one.

> So whether you like nested functions or not,
> there is some evidence here that the current syntax is somehow
> suboptimal.

The 'problems' of nested defs has nothing to do with decorators in 
particular. Functional programmers use them all the time.

> Another drawback of OSD is that they do not gently collaborate with
> introspection and documentation tools. For instance, let's apply our
> decorator on a silly 'test' function:
>
> #---
>    @old_style_repeat_var(n=5) # 'trace' keeps its default value
>    def test(first=0, last=0):
>      """docstring for undecorated function"""
>      print "test: first=%s last=%s" % (first, last)
> #---
>
> Now, if we try 'help' on it, we get the following answer:
>
> #---
>>>> help(test)
> dummy_func_name_never_used(*args, **keys)
>      docstring for decorated function
> #---

Only because you commented out @wraps.
Again, this is not a problem of the @decorator syntax but of *all* 
wrapping callables. Functools.partial has the same 'problem'.

> '@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.

> reason is that signature copying is not an easy process.

True if you want to do it generically. Copying with modification, as 
functools.partial would have to do, is even worse.

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

There have been proposals but no consensus on a decorator or decolib 
module for the stdlib. I second the other recommendations to make your 
proposal available on the cookbook site, etc.

-- 
Terry Jan Reedy




More information about the Python-ideas mailing list