Bananach
Bananach

Reputation: 2311

Python: use callable object instead of local function as wrapper within decorator

I know how to use locally defined functions in decorators. I do this in decorator1 below.

However, I don't find it very clean, and I experienced pickling problems with it.

I would thus like my decorator to return a callable object. However, I am running into problems: If I execute the following code

def decorator1(f):
    def wrapped(*args,**kwargs):
        print(f,' is being called with ',args,kwargs)
        return f(*args,**kwargs)
    return wrapped

def decorator2(f):
    return Wrapped(f)

class Wrapped():
    def __init__(self,f):
        self.f=f
    def __call__(self,*args,**kwargs):
        print(self.f,' is being called with ',args,kwargs)
        return self.f(*args,**kwargs)

class A():
    @decorator1
    def foo1(self,x):
        return x+1

    @decorator2
    def foo2(self,x):
        return x+1

a=A()
a.foo1(0)
a.foo2(0)

I get

<function A.foo1 at 0x7efe64641f28>  is being called with  (<__main__.A object at 0x7efe65b3b3c8>, 0) {}
<function A.foo2 at 0x7efe6464c0d0>  is being called with  (0,) {}
Traceback (most recent call last):
  File "/home/wolfersf/Dropbox/Professional/Projects/swutil/swutil/test.py", line 28, in <module>
    a.foo2(0)
  File "/home/wolfersf/Dropbox/Professional/Projects/swutil/swutil/test.py", line 15, in __call__
    return self.f(*args,**kwargs)
TypeError: foo2() missing 1 required positional argument: 'x' 

It seems that a is not passed in *args of <Wrapped_instance>.__call__.

I would have thought that a.foo2(0) translates to <a.foo2>(a,0) translates to <Wrapped_instance>(a,0) translates to <Wrapped_instance>.__call__(a,0) translates to <<Wrapped_instance>.__call__>(<Wrapped_instance>,a,0)

What am I doing wrong?

Upvotes: 3

Views: 1516

Answers (1)

Bananach
Bananach

Reputation: 2311

Overwriting __get__ as below does the job, though I am still not sure exactly why my original attempt failed.

class Wrapped():
    def __init__(self,f):
        self.f=f
    def __call__(self,*args,**kwargs):
        print(self.f,' is being called with ',args,kwargs)
        return self.f(*args,**kwargs)
    def __get__(self,obj,type=None):
        return functools.partial(self,obj)

Upvotes: 2

Related Questions