Method vs. Class Method Quirks in Python 3

A code snippet in Python 3 below has been discussed recently on the internet:

In [1]: class A:
   ...:     def method(self):
   ...:         pass
   ...: class B:
   ...:     @classmethod
   ...:     def method(cls):
   ...:         pass

In [2]: A.method is A.method
Out[2]: True

In [3]: B.method is B.method
Out[3]: False

The question is why A.method returns the same object on each call while B.method returns the new one?

Let’s explore first what A.method really is:

In [4]: A.method
Out[4]: <function __main__.A.method(self)>

In [5]: type(A.method)
Out[5]: function

In [6]: import inspect

In [7]: inspect.ismethod(A.method)
Out[7]: False

Essentially, what that means is that A.method is a function, i.e. unbound method of the class A. A great way to check if a method is indeed unbound is to check if its function object and a class instance exist:

In [13]: hasattr(A.method, '__func__')
Out[13]: False

In [14]: hasattr(A.method, '__self__')
Out[14]: False

See Python documentation for more details, specifically an instance method subsection of the Data model section.

On the other hand, B.method is a class method. It returns an instance of the bound method object. Let’s check it:

In [15]: B.method
Out[15]: <bound method B.method of <class '__main__.B'>>

In [16]: type(B.method)
Out[16]: method

In [17]: inspect.ismethod(B.method)
Out[17]: True

In [18]: hasattr(B.method, '__func__')
Out[18]: True

In [19]: hasattr(B.method, '__self__')
Out[19]: True

In [20]: B.method.__self__ is B
Out[20]: True

In [21]: B.method.__func__
Out[21]: <function __main__.B.method(cls)>

That means on each B.method invocation a new object is created. You can achieve the same behaviour with A.method once the method gets bound:

In [22]: a = A()

In [23]: id(a.method)
Out[23]: 140613204502216

In [24]: id(a.method)
Out[24]: 140613210350280

In [25]: id(a.method)
Out[25]: 140613209057096

In [26]: a.method is a.method
Out[26]: False

Again, every time you call a.method a new bound method object is created.

The example above doesn’t apply to Python 2.7 though. It seems that Python 2.7 had different implementation, so that A.method would return different objects every time it’s called.