18

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?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Joe M.
  • 609
  • 3
  • 11

2 Answers2

14

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.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • So Adder2.__call__ returns a bound method? Because Adder2.__call__.im_self == None though inspect.ismethod(Adder2.__call__) == True . And I have found [here](http://stackoverflow.com/questions/53225/how-do-you-check-whether-a-python-method-is-bound-or-not) that bound methods are the ones that f.im_self != None. Can you explain? – Joe M. Sep 19 '12 at 11:42
  • @JoeM.: No, I corrected the answer. It returns an unbound method because you are retrieving it from `Adder2`, not from the instance. – Martijn Pieters Sep 19 '12 at 11:42
  • Yes, but then I do not understand, why the ismethod in the second case does not print False as well - because the Adder2.__call__ is unbound.. (I have no idea how is inspect.ismethod defined, but I thought it should return True iff the function is bound) – Joe M. Sep 19 '12 at 11:46
  • @JoeM.: `ismethod` returns True for both bound and unbound methods. – Martijn Pieters Sep 19 '12 at 11:48
  • @JoeM.: Ah, I see, the documentation for that is misleading. Underneath, it's simply a test for `isinstance(object, types.MethodType)` which applies to both bound and unbound methods. – Martijn Pieters Sep 19 '12 at 11:50
  • Ok, but why then does the first example print False? You said that "Your decorator, which wrapped a function at class building time, prints False because it was passed an unbound function." I do not understand the behavior of ismethod much now. (the doc says that it should return True iff the function is bound, but the criterion seems to be different). Can you explain, please? – Joe M. Sep 19 '12 at 11:52
  • Ok, now I think I got it. Thanks! – Joe M. Sep 19 '12 at 11:57
  • @JoeM.: The word unbound can be a bit misleading there, the important part is `method` vs. `function`. A `method` is either bound or unbound, a `function` is wrapped in a `method` when the function `__get__()` is called passing in a class, and optionally, an instance. I've clarified the language a little. – Martijn Pieters Sep 19 '12 at 12:11
6

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.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • But `inspect.ismethod` is supposed to return `True` only for bound methods, so this does not explain the second case. – Janne Karila Sep 19 '12 at 10:54
  • Indeed, in Python 3, `__get__` on a function with `instance` set to None returns the function itself, not an unbound method. Put differently, methods are always bound in python 3, `__get__` only returns a method when the `instance` argument is not None. – Martijn Pieters Sep 19 '12 at 10:59
  • 1
    @JanneKarila: Despite what [the docs say](http://docs.python.org/library/inspect.html#inspect.ismethod), `inspect.ismethod` returns True even for unbound methods. Try it! – unutbu Sep 19 '12 at 10:59
  • @unutbu unbound methods are methods as well, and distinct from functions. Let `f` be a function. Then `f.__get__(None, int)` returns an unbound method, while `f.__get__(4)` returns a bethod bound to the object `4`. – glglgl Sep 19 '12 at 11:22
  • I do not understand why the second example then returns True, if you are saying that the Adder2.__call__ returns an unbound method - shouldn't it return False? – Joe M. Sep 19 '12 at 11:25
  • @JoeM.: In the second example you are sending `Adder2.__call__` to `deco`. `Adder2.__call__` is an unbound method. So `inspect.ismethod` returns True. – unutbu Sep 19 '12 at 11:27