Python Multiple inheritance and super() function for dummies
Multiple inheritance is a feature in which a class can inherit characteristics from more than one parent class.
If we want to add the same methods to a number of classes we can do that by copying methods to each class, but this is against DRY principle.
Also we can do that with decorators but this is not convenient for big amount of application logic.
The better solution in this case - use mixin, for Python it’s multiple inheritence.
First parent of
does not change the method we are looking for.
But the second - (
Bird) does. What
lay_eggs() will print?
And what it will print for
Solution see below.
The puzzle above based on (diamond problem), this is feature of multiple inheritance as it is, not for Python only.
As you can see on picture, if a number of classes has the same parent and the same child, inheritance tree will have ‘diamonds’ (rhombs).
In Python 3 all classes without parent in fact inherits from
object, so any case of multiple inheritance in Python 3 has diamonds in
inheritance tree because of the common parent -
C3 Algorithm Python 3 (MRO)
To look for inherited methods and attributes, Python use C3 MRO algorithm.
MRO stands for “method resolution order”.
Very simple C3 explanation:
add to the list all object’s parents
- to the end of the list add parent’s parents
if some class added more ones we remove all the occurrences except last one.
As a result we have search order in which we look for inherited methon by parent layers - we do not look deeper before we look in all upper layer parents.
For example for class
[Mammal, Bird, Dinosaur, Vertebrate].
So we look in
And only after that - in
C3 gives you overridden method if it is overridden in any of ancestors despite their order in inheritance list of your class.
So the puzzle solution for Python 3 -
True in both cases, we never get
Python 2 use other MRO algorithm (
It drills deeper and deeper to the end of hierarchy for each parent by their
order in inheritance list.
Python 2 MRO:
[Mammal, Vertebrate, Bird, Dinosaur].
Mammal if will look into
And puzzle solution for Python 2 -
But in Python 2 we can use
new-style classes and in this case it use the
C3 MRO as Python 3. To use
new-style classes in Python 2 you should
In Python 3 this is default - if a class inheritance list is empty
the class inherits from
With MRO as in Python 2 (
deep first), we could not use multiple inheritance
at all because of this Python 3 feature - default parent
So in Python 3 we always have diamond in inheritance tree for objects with
multiple inheritance because all classes have the same grad-..-grand parent.
Function super() implements cooperative inheritance.
Do not be fooled by the name -
super() it is not class parent!
This is class next by MRO list.
As we will see below it can be sibling class. And a class behaviour will change if we add it to different classes.
In some cases this is just intuitively right and good. But that can be very confusing in other cases and can be a source of hard to discover bug.
Cooperative multiple inheritance with super()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Vertebrate: def __init__(self): print('Vertebrate.__init__()') class Bird(Vertebrate): def __init__(self): print('Bird.__init__()') super().__init__() class Mammal(Vertebrate): def __init__(self): print('Mammal.__init__()') super().__init__() class Platypus(Mammal, Bird): def __init__(self): print('Platypus.__init__()') super().__init__() duckbill = Platypus()
Platypus.__init__() Mammal.__init__() Bird.__init__() Vertebrate.__init__()
‘Cooperative’ means everybody has to follow rules.
If any of child class won’t call
super()), parent method won’t be
called at all even if we see that some other child do call parent.
For example lets remove
super() call from
Bird (line 8 in the listing above).
We will have different results:
Platypus.__init__() Mammal.__init__() Bird.__init__()
This is because in fact
Mammal.__init__ call next in MRO list class
Bird), and not the parent
The parent was called by
Bird, but we removed that call.
In the code below I added args into
__init__, so the error looks more obvious.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Vertebrate: def __init__(self): print('Vertebrate.__init__()') class Bird(Vertebrate): def __init__(self, beak_length): print('Bird.__init__()') super().__init__() class Mammal(Vertebrate): def __init__(self, hair_length): print('Mammal.__init__()') super().__init__() class Platypus(Mammal, Bird): def __init__(self): print('Platypus.__init__()') super().__init__(1) duckbill = Platypus()
Platypus.__init__() Mammal.__init__() ... File "animal_class_tree_arguments.py", line 13, in __init__ super().__init__() TypeError: __init__() missing 1 required positional argument: 'beak_length'
Now we see that in
Mammal the line
Remarks about super()
Even if parent implements all necessary
it won’t work in
Example below illustrates that for
It works in child but not in
kid: 0 ... TypeError: 'super' object is not subscriptable