Multiple Inheritance in Python
2016-12-21
Multiple inheritance is supported but usually not recommended in Python. In my own work, I certainly avoid it if I can. Nevertheless, I think it is important for Python developers to understand what multiple inheritance is and how it is implemented.
A simple example of inheritance
Consider the following snippet of code:
class Person(object):
def __init__(self):
self.age = 41
def get_age(self):
return self.age
class Tourist(Person):
pass
t = Tourist()
print t.get_age()
The Tourist class inherits from the Person class, as indicated by the syntax:
class <class name>(<classes to inherit from>)
Inheritance allows the call to the method get_age() on variable t (which is of type Tourist) even if the class Tourist does not implement an __init__() method or a get_age() method. Upon instantiation in line 11, the interpreter finds that there is no __init__() method in Tourist and looks at any inherited class definitions. In this instance, it finds an implementation in the inherited class Person. The age attribute in the Tourist object in t is then assigned the value 41.
After the interpreter goes through a similar process to address the call to t.get_age(), the program prints out 41.
An example of multiple inheritance
Now consider the following:
class Person(object):
def __init__(self):
self.age = 41
def get_age(self):
return self.age
class Japanese(Person):
pass
class Korean(Person):
def __init__(self):
self.age = 42
class Tourist(Japanese, Korean):
pass
t = Tourist()
print t.get_age()
The first thing to note is that the class Tourist now inherits from two other classes: Japanese and Korean. Both Japanese and Korean inherit from Person. However, Korean overrides Person’s implementation of __init__(). Suddenly, it is not so simple to reason about which value will be printed. Will it be the Japanese/Person age or Korean age?
Arriving at the answer
To arrive at the answer, we need to look at something called the Method Resolution Order (MRO). Wikipedia defines it as “an algorithm used primarily to obtain the order in which methods should be inherited (the “linearization”) in the presence of multiple inheritance.”
By appending print Tourist.mro() at the end of our code, we can see the MRO used by the interpreter for class Tourist:
[<class ‘main.Tourist’>, <class ‘main.Japanese’>, <class ‘main.Korean’>, <class ‘main.Person’>, <type ‘object’>]
When the Python interpreter looks inside the Tourist object, it follows the following order:
Tourist -> Japanese -> Korean -> Person -> object
Note that the interpreter does not traverse deeper into Person until it has looked at both Japanese and Korean. This is a popular algorithm called breadth-first search.
The interpreter finds an implementation of __init__() as soon as it reaches Korean, and so the value 42 is printed. Had the interpreter instead performed a depth-first search, then it would have arrived at the value 41. In fact: this was actually the case up to Python 2.1.
TL/DR
Multiple inheritance in Python: “Learn it in depth, and then avoid using it as much as possible” (Vasquez). I would avoid it in other languages too.