Search code examples
pythonpython-2.7python-decorators

Decorating a method of a class causes an unbound method TypeError in 2.7


Whenever I decorate a method of a class outside the class definition and use it, it throws me a TypeError saying unbound method with class instance as first argument .

I am setting back the decorated method using setattr() method. For eg:

class A(object):

    @classmethod
    def demo_method(cls, a):
        print a

def decorator(function):

    from functools import wraps
    @wraps(function)
    def wrapper(*args, **kwargs):
        return_value = function(*args, **kwargs)
        return return_value

    return wrapper

setattr(A, 'demo_method', decorator(A.demo_method))

A.demo_method(1)

It throws following error:

TypeErrorTraceback (most recent call last)
<ipython-input-13-447ee51677f4> in <module>()
     17 setattr(A, 'demo_method', decorator(A.demo_method))
     18 
---> 19 A.demo_method(1)
     20 

TypeError: unbound method demo_method() must be called with A instance as first argument (got int instance instead)

Solution

  • The behavior you're seeing is actually not related to the use of decorator itself. You can also say just this:

    class C(object):
        pass
    
    setattr(C, 'm', lambda a: a)  # actually also just: C.m = lambda a: a
    print(C.m(1))
    

    And you would still get essentially the same error. You can read up a bit more in Python 2 documentation for User-defined methods, namely these bits:

    Note that the transformation from function object to (unbound or bound) method object happens each time the attribute is retrieved from the class or instance. ... Also notice that this transformation only happens for user-defined functions; other callable objects (and all non-callable objects) are retrieved without transformation. It is also important to note that user-defined functions which are attributes of a class instance are not converted to bound methods; this only happens when the function is an attribute of the class.

    And the exception you're hitting because:

    When an unbound user-defined method object is called, the underlying function (im_func) is called, with the restriction that the first argument must be an instance of the proper class (im_class) or of a derived class thereof.

    If you had closer look at C.m, you would see:

    >>> print(C.m)
    <unbound method C.<lambda>>
    >>> print(C().m)
    <bound method C.<lambda> of <__main__.C object at 0x7f6f6c5fa850>>
    

    Whereas with Python 3 (as was hinted in a comment this construct would have passed) the behavior would be:

    >>> print(C.m)
    <function <lambda> at 0x7f69fbe8d0d0>
    >>> print(C().m)
    <bound method <lambda> of <__main__.C object at 0x7f69fbe89940>>
    

    Note C.m in the latter case is still accessed as a (plain) function and not an (unbound) method.

    I am not entirely sure what exactly you're ultimately going for and what would be the most helpful direction, but to just get the same behavior you'd get in Python 3 for this code, you could change (not that I'd recommend it) C.m() call to C.m.__func__() one to access the underlying function directly. Or with your example:

    A.demo_method.__func__(1)