According to the docs on inheritance:
Derived classes may override methods of their base classes. Because methods have no special privileges when calling other methods of the same object, a method of a base class that calls another method defined in the same base class may end up calling a method of a derived class that overrides it.
How does that happen? Can someone illustrate this concept with a simple example?
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__
for get_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 the mro
chain, so get_spam
is found in Spam
'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__
for produce_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"
To illustrate how it works consider these two classes:
class Parent(object):
def eat(self):
print("I don't want to eat that {}.".format(self.takefrompocket()))
def takefrompocket(self):
return 'apple'
def __getattribute__(self, name):
print('Looking for:', name)
method_to_use = object.__getattribute__(self, name)
print('Found method:', method_to_use)
return method_to_use
class Child(Parent):
def takefrompocket(self):
return 'salad'
The __getattribute__
method is responsible in new-style-classes (like all classes in python3) for the attribute lookup. It is just implemented to print
what each lookup does - normally you don't want to and shouldn't implement it yourself. The lookup follows pythons method resolution order (MRO) just if you are really interested.
>>> some_kid = Child()
>>> some_kid.eat()
Looking for: eat
Found method: <bound method Parent.eat of <__main__.Child object at 0x0000027BCA4EEA58>>
Looking for: takefrompocket
Found method: <bound method Child.takefrompocket of <__main__.Child object at 0x0000027BCA4EEA58>>
I don't want to eat that salad.
So when you want to use eat
then it uses Parent.eat
in this example. But self.takefrompocket
is used from Child
.
>>> some_parent = Parent()
>>> some_parent.eat()
Looking for: eat
Found method: <bound method Parent.eat of <__main__.Parent object at 0x0000027BCA4EE358>>
Looking for: takefrompocket
Found method: <bound method Parent.takefrompocket of <__main__.Parent object at 0x0000027BCA4EE358>>
I don't want to eat that apple.
Here both methods are taken from Parent
. Inherited classes don't (generally) interfere with their ancestors!
If your child class doesn't implement the method, raise an exception!
class Base(object):
def something (self):
raise ('Not implemented')