Search code examples
pythondecoratorinspection

Decorator changing function status from method to function


[Updated]: Answer inline below question

I have an inspecting program and one objective is for logic in a decorator to know whether the function it is decorating is a class method or regular function. This is failing in a strange way. Below is code run in Python 2.6:

def decorate(f):
    print 'decorator thinks function is', f
    return f

class Test(object):
    @decorate
    def test_call(self):
        pass

if __name__ == '__main__':
    Test().test_call()
    print 'main thinks function is', Test().test_call

Then on execution:

decorator thinks function is <function test_call at 0x10041cd70>
main thinks function is <bound method Test.test_call of <__main__.Test object at 0x100425a90>>

Any clue on what's going wrong, and if it is possible for @decorate to correctly infer that test_call is a method?

[Answer] carl's answer below is nearly perfect. I had a problem when using the decorator on a method that subclasses call. I adapted his code to include a im_func comparison on superclass members:

ismethod = False
for item in inspect.getmro(type(args[0])):
    for x in inspect.getmembers(item):
        if 'im_func' in dir(x[1]):
            ismethod = x[1].im_func == newf
            if ismethod:
                break
    else:
        continue
    break

Solution

  • As others have said, a function is decorated before it is bound, so you cannot directly determine whether it's a 'method' or 'function'.

    A reasonable way to determine if a function is a method or not is to check whether 'self' is the first parameter. While not foolproof, most Python code adheres to this convention:

    import inspect
    ismethod = inspect.getargspec(method).args[0] == 'self'
    

    Here's a convoluted way that seems to automatically figure out whether the method is a bound or not. Works for a few simple cases on CPython 2.6, but no promises. It decides a function is a method if the first argument to is an object with the decorated function bound to it.

    import inspect
    
    def decorate(f):
        def detect(*args, **kwargs):
            try:
                members = inspect.getmembers(args[0])
                members = (x[1].im_func for x in members if 'im_func' in dir(x[1]))
                ismethod = detect in members
            except:
                ismethod = False
            print ismethod
    
            return f(*args, **kwargs)
        return detect
    
    @decorate
    def foo():
        pass
    
    class bar(object):
        @decorate
        def baz(self):
            pass
    
    foo() # prints False
    bar().baz() # prints True