7

When creating decorators for use on class methods, I'm having trouble when the decorator mechanism is a class rather than a function/closure. When the class form is used, my decorator doesn't get treated as a bound method.

Generally I prefer to use the function form for decorators but in this case I have to use an existing class to implement what I need.

This seems as though it might be related to python-decorator-makes-function-forget-that-it-belongs-to-a-class but why does it work just fine for the function form?

Here is the simplest example I could make to show all goings on. Sorry about the amount of code:

def decorator1(dec_param):
    def decorator(function):
        print 'decorator1 decoratoring:', function
        def wrapper(*args):
            print 'wrapper(%s) dec_param=%s' % (args, dec_param)
            function(*args)
        return wrapper
    return decorator

class WrapperClass(object):
    def __init__(self, function, dec_param):
        print 'WrapperClass.__init__ function=%s dec_param=%s' % (function, dec_param)
        self.function = function
        self.dec_param = dec_param

    def __call__(self, *args):
        print 'WrapperClass.__call__(%s, %s) dec_param=%s' % (self, args, self.dec_param)
        self.function(*args)

def decorator2(dec_param):
    def decorator(function):
        print 'decorator2 decoratoring:', function
        return WrapperClass(function, dec_param)
    return decorator

class Test(object):
    @decorator1(dec_param=123)
    def member1(self, value=1):
        print 'Test.member1(%s, %s)' % (self, value)

    @decorator2(dec_param=456)
    def member2(self, value=2):
        print 'Test.member2(%s, %s)' % (self, value)

@decorator1(dec_param=123)
def free1(value=1):
    print 'free1(%s)' % (value)

@decorator2(dec_param=456)
def free2(value=2):
    print 'free2(%s)' % (value)

test = Test()
print '\n====member1===='
test.member1(11)

print '\n====member2===='
test.member2(22)

print '\n====free1===='
free1(11)

print '\n====free2===='
free2(22)

Output:

decorator1 decoratoring: <function member1 at 0x3aba30>
decorator2 decoratoring: <function member2 at 0x3ab8b0>
WrapperClass.__init__ function=<function member2 at 0x3ab8b0> dec_param=456
decorator1 decoratoring: <function free1 at 0x3ab9f0>
decorator2 decoratoring: <function free2 at 0x3ab970>
WrapperClass.__init__ function=<function free2 at 0x3ab970> dec_param=456

====member1====
wrapper((<__main__.Test object at 0x3af5f0>, 11)) dec_param=123
Test.member1(<__main__.Test object at 0x3af5f0>, 11)

====member2====
WrapperClass.__call__(<__main__.WrapperClass object at 0x3af590>, (22,)) dec_param=456
Test.member2(22, 2)        <<<- Badness HERE!

====free1====
wrapper((11,)) dec_param=123
free1(11)

====free2====
WrapperClass.__call__(<__main__.WrapperClass object at 0x3af630>, (22,)) dec_param=456
free2(22)
Community
  • 1
  • 1
  • 1
    I would advise renaming the question. This actually isn't really related to decorators, but is rather about adding a function object as a class method – Casebash Nov 05 '09 at 02:02
  • Generally (although not always), when a question is that long, it can be simplified by isolating the problem. For example, if you had tried manually annotating the class, you would have realised that it had nothing to do with decorators and it probably would have been quicker than typing out all of that code – Casebash Mar 19 '10 at 23:57

1 Answers1

10

Your WrapperClass needs to be a descriptor (just like a function is!), i.e., supply appropriate special methods __get__ and __set__. This how-to guide teaches all you need to know about that!-)

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395