super and multiple inheritance
Before we get into multiple inheritance and super... Warning, this can get pretty weird and complicated.
First off, what is multiple inheritance? So far the example code has covered a single child class inheriting from a single parent class. In multiple inheritance, there's more than one parent class. A child class can inherit from 2, 3, 10, etc. parent classes.
Here is where the benefits of super become more clear. In addition to saving keystrokes of referencing the different parent class names, there are nuanced benefits to using super with multiple inheritance patterns. In short, if you're going to use multiple inheritance, use super.
Multiple inheritance without super
Let's look at an example of multiple inheritance that avoids modifying any parent methods and in turn avoids super.
Input
class B:
def b(self):
print('b')
class C:
def c(self):
print('c')
class D(B, C):
def d(self):
print('d')
d = D()
d.b()
d.c()
d.d()
Output
Multiple-resolution order
This output isn't too surprising given the concept of multiple inheritance. D inherited the methods x and z from its parent classes, and everything is good in the world... for now.
So what if both B and C both had a method with the same name? This is where a concept called 'multiple-resolution order' comes into play or MRO for short. The MRO of a child class is what decides where Python will look for a given method, and which method will be called when there's a conflict.
Let's look at an example.
Input
class B:
def x(self):
print('x: B')
class C:
def x(self):
print('x: C')
class D(B, C):
pass
d = D()
d.x()
print(D.mro())
Output
When we call the inherited x method, we only see the output inherited from B. We can see the MRO of our D class by calling the mro class method. From the D.mro() output we learn the following: our program will try to call D methods by default, then resort to B, then C, and finally object. If it's not found in any of those places, then we'll get the error that D doesn't have the method we asked for.
It's worth noting that by default, every class inherits from object, and it's on the tail-end of every MRO.
Multiple inheritance, super, and the diamond problem
Below is an example of using super to handle MRO of init in a way that's beneficial. In the example, we create a series of text processing classes and combine their functionality in another class with multiple inheritance. We'll create 4 classes, and the structure for inheritance will follow the structure in the below diagram.
Note: This structure is for illustrative purposes, and, barring restraints, there would be better ways to implement this logic.

This is actually an example of the 'diamond problem' of multiple inheritance. Its name is of course based on the shape of its design, and the fact that it's a fairly confusing problem.
Below the design is written out with the use of super.
Input
class Tokenizer:
"""Tokenize text"""
def __init__(self, text):
print('Start Tokenizer.__init__()')
self.tokens = text.split()
print('End Tokenizer.__init__()')
class WordCounter(Tokenizer):
"""Count words in text"""
def __init__(self, text):
print('Start WordCounter.__init__()')
super().__init__(text)
self.word_count = len(self.tokens)
print('End WordCounter.__init__()')
class Vocabulary(Tokenizer):
"""Find unique words in text"""
def __init__(self, text):
print('Start init Vocabulary.__init__()')
super().__init__(text)
self.vocab = set(self.tokens)
print('End init Vocabulary.__init__()')
class TextDescriber(WordCounter, Vocabulary):
"""Describe text with multiple metrics"""
def __init__(self, text):
print('Start init TextDescriber.__init__()')
super().__init__(text)
print('End init TextDescriber.__init__()')
td = TextDescriber('row row row your boat')
print('--------')
print(td.tokens)
print(td.vocab)
print(td.word_count)
Output
Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
Start Tokenizer.__init__()
End Tokenizer.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
--------
['row', 'row', 'row', 'your', 'boat']
{'boat', 'your', 'row'}
5
First off, we see the TextDescriber class has inherited all the attributes of the class family tree. Thanks to multiple inheritance we can 'combine' the functionality of more than one class.
Let's now discuss the printouts that came from the class's init methods:
Each __init__ method was called once and only once.
The TextDescriber class inherited from 2 classes that inherit from Tokenizer. Why was Tokenizer.__init__ not called twice?
If we replaced all of our calls to super with the old fashioned way, we would end up with 2 calls to Tokenizer.__init__. The calls to super 'think' through our pattern a little bit more and skips the extra trip to A.
Each __init__ method was started before any of the others were finished.
The order of the starts and finishes of each __init__ is worth noting in case you're attempting to set an attribute that has a naming conflict with another parent class. The attribute will be overwritten, and it can become very confusing.
In our case, we avoided naming conflicts with inherited attributes, so everything is working as expected.
To reiterate, the diamond problem can get complicated fast and lead to unexpected results. With most cases in programming, it's best to avoid complicated designs.
What we learned
- We learned about the
superfunction and how it can be used to replaceParentName.methodin single inheritance. This can be a more maintainable practice. - We learned about multiple inheritance and how we can pass on the functionality of multiple parent classes to a single child class.
- We learned about multiple-resolution order and how it decides what happens in multiple inheritance when there's a naming conflict between parent methods.
- We learned about the diamond problem and saw an example of how the use of
supernavigates the diamond.
Quick overview of inheritance
As you grow your Python projects and packages, you'll inevitably want to utilize classes and apply the DRY (don't-repeat-yourself) principle while doing so. Class inheritance is a fantastic way to create a class based on another class in order to stay DRY. This post will cover more advanced concepts of inheritance, and basic inheritance won't be covered in depth. We'll go over a quick intro, but there are much better, detailed introductions out there. Here are some resources to get started: Object-Oriented Programming in Python course & Python Object-Oriented Programming (OOP): Tutorial.
So what is class inheritance? Similarly to genetics, a child class can 'inherit' attributes and methods from a parent. Let's jump right into some code for an example. In the below code block we'll demonstrate inheritance with a
Childclass inheriting from aParentclass.Input
Output
We see that the
Childclass 'inherited' attributes and methods from theParentclass. Without any work on our part, theParent.parent_methodis a part of theChildclass. To get the benefits of theParent.__init__()method we needed to explicitly call the method and passself. This is because when we added an__init__method toChild, we overwrote the inherited__init__.With that brief, very non-comprehensive overview out of the way lets jump into the meat of the post.
Intro to
superIn the simplest case, the
superfunction can be used to replace the explicit call toParent.__init__(self). Our intro example from the first section can be rewritten withsuperas seen below. Note, that the below code block is written in Python 3, earlier versions use a slightly different syntax. Additionally, the output has been omitted since it's identical to the first code block.To be honest,
superin this case gives us little, if any, advantage. Depending on the name of our parent class we might save some keystrokes, and we don't have to passselfto the call to__init__. Below are some pros and cons of the use ofsuperin single inheritance cases.Cons
It can be argued that using
superhere makes the code less explicit. Making code less explicit violates The Zen of Python, which states, "Explicit is better than implicit."Pros
There is a maintainability argument that can be made for
supereven in single inheritance. If for whatever reason your child class changes its inheritance pattern (i.e., parent class changes or there's a shift to multiple inheritance) then there's no need find and replace all the lingering references toParentClass.method_name(); the use ofsuperwill allow all the changes to flow through with the change in theclassstatement.