Peter Smit
Peter Smit

Reputation: 28746

Are decorators that are classes called different than decorators that are functions?

Consider the following to decorators

class MethodDecoratorC(object):
    def __init__(self,func):
        self.func = func
    def __call__(self,*args,**kwargs):
        print(len(args))
        print(len(kwargs))
        self.func(*args,**kwargs)

def method_decorator_f(func):
    def wrapped_func(*args,**kwargs):
        print(len(args))
        print(len(kwargs))
        func(*args,**kwargs)
    return wrapped_func

They look to do exactly the same, and for functions that's true:

@MethodDecoratorC
def test_method_c(a):
    print(a)

@method_decorator_f
def test_method_f(a):
    print(a)

test_method_f("Hello World! f")
test_method_c("Hello World! c")

prints:

1
0
Hello World! f
1
0
Hello World! c

For methods however, something very strange happens:

class TestClass(object):
    @MethodDecoratorC
    def test_method_c(self,a):
        print(a)

    @method_decorator_f
    def test_method_f(self,a):
        print(a)

t = TestClass()
t.test_method_f("Hello World! f")
t.test_method_c("Hello World! c")

prints:

2
0
Hello World! f
1
0
Traceback (most recent call last):
  File "test5.py", line 40, in <module>
    t.test_method_c("Hello World! c")
  File "test5.py", line 8, in __call__
    self.func(*args,**kwargs)
TypeError: test_method_c() takes exactly 2 arguments (1 given)

Not much expected! Somehow the TestClass object is not transferred as an argument to the __call__ method of my decorator object.

Why this difference? And is there a way that I can still obtain the object in my class style decorator?

Upvotes: 2

Views: 103

Answers (1)

user395760
user395760

Reputation:

self being bound to the first argument for instance methods works only because methods are wrapped in descriptors. When obj.meth is requested, not found in the object and then found in the class, the descriptor's __get__ method is called with some information including the object, and returns a wrapper around the actual method object that, when called, calls the underlying method with the object as additonal/first argument (self).

These descriptors are only added for actual functions, not for other callable objects. To make a class with a __call__ method work like a method, you'll have to implement a __get__ method (see link above).

Upvotes: 2

Related Questions