Decorating methods - where do my arguments go?

George Sakkis george.sakkis at gmail.com
Sat May 9 04:37:32 EDT 2009


On May 8, 11:33 am, Mikael Olofsson <mik... at isy.liu.se> wrote:

> Hi all!
>
> I have long tried to avoid decorators, but now I find myself in a
> situation where I think they can help. I seem to be able to decorate
> functions, but I fail miserably when trying to decorate methods. The
> information I have been able to find on-line focuses on decorating
> functions, and do not mention any special problem regarding methods.
>
> Consider the following example. I start by defining a simple decorator:
>
>  >>> class test_decorator(object):
> ...     def __init__(self,func):
> ...         self._func = func
> ...     def __call__(self, *args):
> ...         print 'Decorator:', args
> ...         self._func(*args)
>
> Then I decorate a function:
>
>  >>> @test_decorator
> ... def func(*args):
> ...     print 'Function: ', args
>
> Let's try that:
>
>  >>> func(1,2,3)
> Decorator: (1, 2, 3)
> Function:  (1, 2, 3)
>
> OK! That was what I expected. Let's decorate a method:
>
>  >>> class cls(object):
> ...     @test_decorator
> ...     def meth(self,*args):
> ...         print 'Method:   ', args
>
> Then try that:
>
>  >>> cls().meth(1,2,3)
> Decorator: (1, 2, 3)
> Method:    (2, 3)
>
> Oops! That's weird. I had expected - or at least wanted - the same
> result as for the function above.
>
> Where did the first argument go? If it was sent to the original method
> meth as the real first argument (self), then why did I not get an exception?
>
> More importantly: How do I write a decorator that does not drop arguments?
>
> I guess it all has to do with the fact that the returned callable isn't
> a method of cls. Is it possible to write a decorator that returns a
> callable that is a method of cls, when used on methods in cls?

Yes, just return an actual function from the decorator instead of a
callable object:

def test_decorator2(func):
    def wrapper(*args):
        print 'Decorator2:', args
        func(*args)
    return wrapper


class cls(object):
    @test_decorator
    def meth(self,*args):
        print 'Method:   ', args

    @test_decorator2
    def meth2(self,*args):
        print 'Method2:   ', args

>>> cls.meth
<__main__.test_decorator object at 0x87663cc>

>>> cls.meth2
<unbound method cls.wrapper>

>>> cls().meth2(1,2,3)
Decorator2: (<__main__.cls object at 0x8766ecc>, 1, 2, 3)
Method2:    (1, 2, 3)

The reason this works is that functions are already descriptors, so
you don't have to explicitly define __get__() as you would for a new
callable class (see Peter's example).

HTH,
George



More information about the Python-list mailing list