Understanding the way and the order in which Python resolves functions is important. You need to know where your code is going to call, when you call it and things can get messy as soon as you start playing with classes inheriting from multiple other classes.
In this tutorial we’ll be looking at how Python 3 handles its
MRO by using a little something called
The Problems with Inheritance
Imagine you were implementing a programming language that featured inheritance. When you first approach this topic you decide: a child class will have all of the functions and attributes that it’s parent classes should have!
Now this may work for the vast majority of scenarios but what would happen if two parent classes both implemented the same function or attribute? How do you decide what function or attribute takes precedence?
This is known as the diamond problem. Some languages such as Scala, use an algorithm called
right-first depth-first searchto solve this. Python 3 uses the
C3 linearization algorithm.
A Pratical Example
Let’s take a look at how
Method Resolution Order or
MRO works in real-terms with a very simple Python program. We’ll define 2 classes that inherit nothing, and a third which inherits both.
What’s special about this is that the 2 classes our
my_super_class class inherits from both define a
What happens when we run this code?
When we run the above code you should see that the
__init__ function is called of our
my_super_class class. It then calls the inherited
test_func(self) function from the its inherited
If we were to switch the order in which our
my_super_class inherits then you’ll see that the
test_func() from the
not_so_awesome_class is called instead. Key takeaway from this point is that ordering of inheritance matters.
Let’s now have a look at this
C3 superclass linearization algorithm and how it works. We can define it as so:
C3 Superclass Linearizationof a class is the sum of the class plus a unique merge of the linearizations of its parents and a list of the parents itself.
This may not mean a lot to most people, so let’s try and demystify what all this means by breaking it down.
C3 does the following:
- It guarantees that base class declaration is preserved
- It guarantees that subclasses appear before base classes
- It guarantees that for every class in a graph of all inherited classes, they adhere to the previous two points
Let’s have a look at the following class inheritance graph from the algo’s wiki:
Basically in this graph
class O does not have any parents and features a number of children:
[A, B, C, D, E]. Our
[A, B, C] classes are the parents to
[D, B, E] for
[D, A] for
K3. Finally class
Z extends from
[K1, K2, K3].
This represents a fairly complex dependency graph and not typically representative of “normal” systems, however it gives us lots of examples to dissect how
Let’s start breaking this down:
||||This is relatively trivial and would be  as it has no parents|
||[A] + merge(l[O], [O])||The linearization of A is A plus the merge of its parent’s,|
|[A, O]||Thus the final linearization equals [A,O]|
||[K1] + merge(L(A), L(B), L©, [A,B,C])||First we need to find the linearizations of
|[K1] + merge([A,O], [B,O, [C,O], [A,B,C]])||Class A is a good candidate as it is the head of the parent list|
|[K1, A] + merge([O], [B, O], [C, O], [B, C])||We shouldn’t choose class
|[K1, A, B] + merge([O], [O], [C,O], [C])||Again
|[K1, A, B, C] + merge([O], [O], [O])||Finally we have to choose class O as all other options are exhausted|
|[K1, A, B, C, O]||Our final solution|
Exercise: Work out the linearization of
L(Z)based on examples above.
Hopefully this article gave you a little bit more insight into how
Method Resolution Order works in Python 3. If you found this useful or require further assistance then please let me know in the comments section below.