Question

I am having trouble understanding why the following happens. I am having a decorator which does nothing except it checks whether a function is a method. I thought I have understood what method in Python is, but obviously, this is not the case:

import inspect

def deco(f):
    def g(*args):
        print inspect.ismethod(f)
        return f(*args)
    return g

class Adder:
    @deco
    def __call__(self, a):
        return a + 1

class Adder2:
    def __call__(self, a):
        return a + 2
Adder2.__call__ = deco(Adder2.__call__)

Now, running the following:

>>> a = Adder()
>>> a(1)
False
2
>>> a2 = Adder2()
>>> a2(1)    
True
3

I would expect for this code to print True two times.

So, decorating the function manually as in the Adder2 is not an exact equivalent to decorating via the @deco function?

Can someone be so glad and explain why this happens?

Was it helpful?

Solution

Inside a class definition, __call__ is a function, not a method. The act of accessing the function through attribute lookup (e.g. using the dot syntax), such as with Adder2.__call__, returns an unbound method. And a2.__call__ returns a bound method (with self bound to a2).

Note that in Python3 the concept of unbound method has been dropped. There, Adder2.__call__ is a function as well.

OTHER TIPS

Methods are functions that are associated with a class. Methods are only created when you retrieve them from an already defined class; a method is a wrapper around a function, with a reference to the class as well (and optionally a reference to the instance).

What happens in the first case is: Python compiles your class definition Adder. It finds the decorator definition and a function. The decorator is passed the function, returning a new function. That function is added to the class definition (stored in the class __dict__). All this time you are dealing with a python function, not a method. That happens later.

When you then call a(1), a lookup reveals that the instance doesn't have a __call__ but the Adder class does, so it is retrieved using __getattribute__(). This finds a function (your deco decorator), which is a descriptor so it's __get__() method is called (so Adder.__call__.__get__(a, Adder)), returning a bound method, which is then called and passed in the 1 value. The method is bound because instance is not None when __get__() is called. Your decorator, which wrapped a function at class building time, prints False because it was passed an unwrapped function to start with.

In the second case, however, you retrieve a method (again via __getattribute__() calling __get__() on the undecorated Adder2.__call__ function), this time unbound (as there is no instance, only a class passed to __get__() (the full call is Adder2.__call__.__get__(None, Adder2)), and you then decorate that method. Now ismethod() prints True.

Note that in Python 3, the latter case changes. In Python 3 there no longer is a concept of an unbound method, only functions and bound methods. The term 'bound' is thus dropped altogether. Your second case would also print False as Adder2.__call__.__get__(None, Adder2) returns a function in that case.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top