In this post, I will show you what multiple inheritance is, how you can use it and what to watch out for. First you will learn about single and multiple inheritance and how you can use them in Python. After this you will learn some multiple inheritance gotchas among which the dreaded diamond problem.
Basic class inheritance
Inheritance is a concept you will probably know when you work with an object oriented language. Here is an example:
class Parent:
def __init__(self, x):
self.x = x
def __str__(self):
return f"x: {self.x}"
class Child(Parent):
pass
parent = Parent("Will")
child = Child("Jaden")
In this example the Child
class inherits from the Parent
class. When you print the parent
and child
to the console you will see their behaviors.
# And print both objects
>>> print(parent)
x: Will
>>> print(child)
x: Jaden
Introduction to multiple inheritance
Python has a powerful multiple inheritance concept. There are some things to look out for though, but I’ll get to that later.
Let’s see a multiple inheritance example:
class Parent1:
@property
def x(self):
return "X"
class Parent2:
@property
def y(self):
return "y"
class Child(Parent1, Parent2):
@property
def z(self):
return "Z"
Easy as that. The child class can inherit from as many base classes as you want.
Run this code to see the result:
>>> child = Child()
>>> print(child.x)
X
>>> print(child.y)
Y
>>> print(child.z)
Z
Parameters with multiple inheritance
The example above is nice, but wouldn’t it be better if you could pass the name when you create an object?
In the next example you pass the names as parameters. All three classes have an __init__
method, and it is interesting to see what happens in the Child
class where you set the y
attribute, and call __init__
on Mom
and Dad
.
class Parent1:
def __init__(self, x):
self.x = x
class Parent2:
def __init__(self, y):
self.y = y
class Child(Parent1, Parent2):
def __init__(self, x, y, z):
Parent1.__init__(self, x)
Parent2.__init__(self, y)
self.z = z
The result is the same, but you gained flexibility by using parameters.
>>> child = Child("Jada", "Will", "Jaden")
>>> print(child.x)
Jada
>>> print(child.y)
Will
>>> print(child.z)
Jaden
Multiple inheritance gotchas
Multiple inheritance in Python looks simple, and it is one of Pythons most powerful features. You have to look out for gotchas that will lead to maintenance hell if you misuse or abuse it.
Here are some things you have to look out for when using multiple inheritance:
Abusing multiple inheritance
Multiple inheritance should be looked at like any other design pattern. So use it with care and only when applicable. There are often better solutions.
Method resolution order
Python uses a resolution order to figure out which implementation to use. When two or more parent classes have the same methods or attributes they will be resolved from left to right.
In the example below Child
inherits from Parent1
and Parent2
which both have a method called a(self)
.
class Parent1:
def __init__(self, x):
self.x = x
def a(self):
print(self.x)
class Parent2:
def __init__(self, y):
self.y = y
def a(self):
print(self.y)
class Child(Parent1, Parent2):
def __init__(self, x, y):
Parent1.__init__(self, x)
Parent2.__init__(self,y)
Because Parent1
is used first, his implementation will be called. Run this code to see the result:
>>> child = Child("Jada", "Will")
>>> print(child.a())
Jada
You can call .__mro__
or .mro()
on a class to figure out the resolution order.
>>> Child.__mro__
(<class '__main__.Child'>, <class '__main__.Parent1'>, <class '__main__.Parent2'>, <class 'object'>)
>>> Child.mro()
(<class '__main__.Child'>, <class '__main__.Parent1'>, <class '__main__.Parent2'>, <class 'object'>)
The diamond problem
The diamond problem, or deadly diamond of death, occurs when multiple parent classes inherit from the same base class which makes the inheritance graph look like a diamond.
When both class A
and class B
or C
implemented the same methods, this will lead to trouble.
Diamond problem example
Because it is so important I wanted to elaborate with an example:
class GrandParent:
def a(self):
print("Diamond problem is bad, mmmmkay")
class Parent1(GrandParent):
def __init__(self, x):
self.x = x
def a(self):
print(self.x)
class Parent2(GrandParent):
def __init__(self, y):
self.y = y
def a(self):
print(self.y)
class Child(Parent1, Parent2):
def __init__(self, x, y):
Parent1.__init__(self, x)
Parent2.__init__(self, y)
How do you think this will be resolved? Your first thought might be to follow the left to right rule, but then you are wrong.
>>> print(Child.__mro__)
(<class '__main__.Child'>, <class '__main__.Parent1'>, <class '__main__.Parent2'>, <class '__main__.GrandParent'>, <class 'object'>)
Now you see the first class in the mro is Parent1
as you would expect, but next it is Parent2
and lastly GrandParent
, which might not be what you expected.
Conclusion
Multiple inheritance is a powerful Python feature. Once you know how it works and when to use it you can do amazing things with it. But on the other hand it can lead to some major issues you need to be aware of.