How do overridden method calls from base-class methods work?
Here's the example you requested. This prints chocolate
.
class Base: def foo(self): print("foo") def bar(self): self.foo()class Derived(Base): def foo(self): print("chocolate")d = Derived()d.bar() # prints "chocolate"
The string chocolate
is printed instead of foo
because Derived
overrides the foo()
function. Even though bar()
is defined in Base
, it ends up calling the Derived
implementation of foo()
instead of the Base
implementation.
How does it work?
When an attribute look-up is performed on an instance of the class, the class dictionary and the dictionaries of its base classes are searched in a certain order (see: Method Resolution Order) for the appropriate method. What is found first is going to get called.
Using the following Spam
example:
class Spam: def produce_spam(self): print("spam") def get_spam(self): self.produce_spam()class SuperSpam(Spam): def produce_spam(self): print("super spam")
Spam
defines the functions produce_spam
and get_spam
. These live in its Spam.__dict__
(class namespace). The sub-class SuperSpam
, by means of inheritance, has access to both these methods. SuperSpam.produce_spam
doesn't replace Spam.produce_spam
, it is simply found first when the look-up for the name 'produce_spam'
is made on one of its instances.
Essentially, the result of inheritance is that the dictionaries of any base classes are also going to get searched if, after an attribute look-up on the sub-class is made, the attribute isn't found in the sub-class's dictionary.
When the function get_spam
is first invoked with:
s = SuperSpam()s.get_spam()
the sequence of events roughly goes like this:
- Look into
SuperSpam
s__dict__
forget_spam
. - Since it isn't found in
SuperSpam
s__dict__
look into the dictionaries of it's base classes (mro
chain). Spam
is next in themro
chain, soget_spam
is found inSpam
's dictionary.
Now, when produce_spam
is looked up in the body of get_spam
with self.produce_spam
, the sequence is much shorter:
- Look into
SuperSpam
's (self
)__dict__
forproduce_spam
. - Find it, get it and call it.
produce_spam
is found in the __dict__
first so that gets fetched.
class Base(): def m1(self): return self.m2() def m2(self): return 'base'class Sub(Base): def m2(self): return 'sub'b = Base()s = Sub()print(b.m1(), s.m1())
prints "base sub"