Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - why can I call a class method with an instance?

New to Python and having done some reading, I'm making some methods in my custom class class methods rather than instance methods.

So I tested my code but I hadn't changed some of the method calls to call the method in the class rather than the instance, but they still worked:

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)

myClass.foo()
thing = myClass()
thing.foo()
thing.bar()

This produces:

class method foo called with __main__.myClass.
class method foo called with __main__.myClass.
instance method bar called with <__main__.myClass instance at 0x389ba4>.

So what I'm wondering is why I can call a class method (foo) on an instance (thing.foo), (although it's the class that gets passed to the method)? It kind of makes sense, as 'thing' is a 'myClass', but I was expecting Python to give an error saying something along the lines of 'foo is a class method and can't be called on an instance'.

Is this just an expected consequence of inheritance with the 'thing' object inheriting the foo method from its superclass?

If I try to call the instance method via the class:

myClass.bar()

then I get:

TypeError: unbound method bar() must be called with myClass instance...

which makes perfect sense.

like image 366
Steve Ives Avatar asked May 12 '15 11:05

Steve Ives


People also ask

Can you call a class method from an instance Python?

To call a class method, put the class as the first argument. Class methods can be can be called from instances and from the class itself. All of these use the same method. The method can use the classes variables and methods.

Can instance methods call class methods?

Not only can they modify object state, instance methods can also access the class itself through the self.

Can you call the base class method without creating an instance in Python?

Static method can be called without creating an object or instance. Simply create the method and call it directly. This is in a sense orthogonal to object orientated programming: we call a method without creating objects.


2 Answers

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.

like image 137
John Avatar answered Oct 26 '22 17:10

John


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).

like image 26
chepner Avatar answered Oct 26 '22 18:10

chepner