Decorators not worth the effort

Steven D'Aprano steve+comp.lang.python at pearwood.info
Fri Sep 14 19:39:28 EDT 2012


On Fri, 14 Sep 2012 15:16:47 -0600, Ian Kelly wrote:

> If only there were a conceptually simpler way to do this.  Actually,
> there is.  I give you: metadecorators!
[code snipped but shown below]
> Which I think is certainly easier to understand than the nested
> functions approach.

Maybe for you, but to me it is a big ball of mud. I have no idea how this 
is supposed to work! At a quick glance, I would have sworn that it 
*can't* work, since simple_decorator needs to see multiple arguments but 
only receives one, the function to be decorated. And yet it does work:

py> from functools import partial
py> def make_wrapper(wrapper):
...     return lambda wrapped: partial(wrapper, wrapped)
...
py> @make_wrapper
... def simple_decorator(func, *args, **kwargs):
...     print "Entering decorated function"
...     result = func(*args, **kwargs)
...     print "Exiting decorated function"
...     return result
...
py> @simple_decorator
... def my_function(a, b, c):
...     """Doc string"""
...     return a+b+c
...
py> my_function(1, 2, 3)
Entering decorated function
Exiting decorated function
6

So to me, this is far more magical than nested functions. If I saw this 
in t requires me to hunt through your library for the "simple function 
buried in a utility module somewhere" (your words), instead of seeing 
everything needed in a single decorator factory function. It requires 
that I understand how partial works, which in my opinion is quite tricky. 
(I never remember how it works or which arguments get curried.)

And the end result is that the decorated function is less debugging-
friendly than I demand: it is an anonymous partial object instead of a 
named function, and the doc string is lost. And it is far from clear to 
me how to modify your recipe to use functools.wraps in order to keep the 
name and docstring, or even whether I *can* use functools.wraps.

I dare say I could answer all those questions with some experimentation 
and research. But I don't think that your "metadecorator" using partial 
is *inherently* more understandable than the standard decorator approach:

def simple_decorator2(func):
    @functools.wraps(func)
    def inner(*args, **kwargs):
        print "Entering decorated function"
        result = func(*args, **kwargs)
        print "Exiting decorated function"
        return result
    return inner

This is no more complex than yours, and it keeps the function name and 
docstring.


> Parameterized decorators are not much more
> difficult this way.  This function:
[snip code]
> And now we have a fancy parameterized decorator that again requires no
> thinking about nested functions at all.

Again, at the cost of throwing away the function name and docstring.

I realise that a lot of this boils down to personal preference, but I 
just don't think that nested functions are necessarily that hard to 
grasp, so I prefer to see as much of the decorator logic to be in one 
place (a nested decorator function) rather than scattered across two 
separate decorators plus partial.




-- 
Steven



More information about the Python-list mailing list