Python - why can I call a class method with an instance?
Let's start with a quick overview of the descriptor protocol. If a class defines a __get__
method, an instance of that class is a descriptor. Accessing a descriptor as the attribute of another object produces the return value of the __get__
method, not the descriptor itself.
A function
is a descriptor; this is how instance methods are implemented. Given
class myClass:
@classmethod:
def foo(cls):
print('Class method foo called with %s.' % (cls,))
def bar(self):
print 'Instance method bar called with %s.'%(self)
c = myClass()
the expression c.bar
is equivalent to
myClass.__dict__['bar'].__get__(c, myClass)
while the expression myClass.bar
is equivalent to the expression
myClass.__dict__['bar'].__get__(None, myClass)
Note the only difference is in the object passed as the first argument to __get__
. The former returns a new method
object, which when called passes c
and its own arguments on to the function bar
. This is why c.bar()
and C.bar(c)
are equivalent. The latter simply returns the function bar
itself.
classmethod
is a type that provides a different implementation of __get__
. This means that c.foo()
and myClass.foo()
call __get__
as before:
# c.foo
myClass.__dict__['foo'].__get__(c, myClass)
# myClass.foo
myClass.__dict__['foo'].__get__(None, myClass)
Now, however, both calls return the same method
object, and this method
object, when called, passes myClass
as the first argument to the original function
object. That is, c.foo()
is equivalent to myClass.foo()
, which
is equivalent to x(myClass)
(where x
is the original function defined before the decoration bound the name foo
to an instance of classmethod
).
You can call it on an instance because @classmethod
is a decorator (it takes a function as an argument and returns a new function).
Here is some relavent information from the Python documentation
It can be called either on the class (such as C.f()) or on an instance (such as C().f()). The instance is ignored except for its class. If a class method is called for a derived class, the derived class object is passed as the implied first argument.
There's also quite a good SO discussion on @classmethod
here.