Python decorator makes function forget that it belongs to a class

Charles Anderson picture Charles Anderson · Nov 20, 2008 · Viewed 24.7k times · Source

I am trying to write a decorator to do logging:

def logger(myFunc):
    def new(*args, **keyargs):
        print 'Entering %s.%s' % (myFunc.im_class.__name__, myFunc.__name__)
        return myFunc(*args, **keyargs)

    return new

class C(object):
    @logger
    def f():
        pass

C().f()

I would like this to print:

Entering C.f

but instead I get this error message:

AttributeError: 'function' object has no attribute 'im_class'

Presumably this is something to do with the scope of 'myFunc' inside 'logger', but I've no idea what.

Answer

Eli Courtwright picture Eli Courtwright · Nov 20, 2008

Claudiu's answer is correct, but you can also cheat by getting the class name off of the self argument. This will give misleading log statements in cases of inheritance, but will tell you the class of the object whose method is being called. For example:

from functools import wraps  # use this to preserve function signatures and docstrings
def logger(func):
    @wraps(func)
    def with_logging(*args, **kwargs):
        print "Entering %s.%s" % (args[0].__class__.__name__, func.__name__)
        return func(*args, **kwargs)
    return with_logging

class C(object):
    @logger
    def f(self):
        pass

C().f()

As I said, this won't work properly in cases where you've inherited a function from a parent class; in this case you might say

class B(C):
    pass

b = B()
b.f()

and get the message Entering B.f where you actually want to get the message Entering C.f since that's the correct class. On the other hand, this might be acceptable, in which case I'd recommend this approach over Claudiu's suggestion.