00:00 Up until now, we’ve designed class hierarchies where each class inherits from at most one other class. This is called single inheritance, but we can go further than that.
00:13 Python is one of the few popular languages that supports this idea of multiple inheritance. Multiple inheritance allows one class to derive from, or inherit from, multiple other classes instead of just one.
But where would this be useful? Well, take this scenario. We want to add a new class to our employee tracking system that represents a temporary secretary. If you remember, the
Secretary class is a
This temporary employee would be different in that it would be treated like a normal
Secretary to the
ProductivitySystem, but it would be paid like an
We could achieve this with single inheritance—for example, by inheriting from
Secretary—so it’s tracked using the inherited
.work() method—and then creating our own
.calculate_payroll() method to calculate payroll as an hourly employee.
But what’s the fun in that? Let’s try inheriting from both classes and just see what happens. I’m here in Visual Studio Code in
01:27 We want to create a new class that represents a temporary secretary, so let’s move to the bottom of this file and create that class.
01:40 To inherit from multiple classes, separate the class names with a comma, just like this.
For now, I’m just going to create an empty class so that it uses its parents’
.__init__() method for object construction. Let’s try this out!
I’m going to move over to
program.py and right underneath the
factory_worker, I’m going to create a new object for this temporary secretary.
I’ll give them an
id and a
and because they’re going to be billed hourly, they’ll need to have an
Let’s run this, and I will finish off this quick video. Ha. Yeah, quick videos aren’t really a thing in this course. We got an exception that says
.__init__() takes four arguments, but we gave it five.
This error can be a bit confusing because it includes the
self argument, which we don’t actually pass into the
.__init__() method ourselves, so just subtract one from each.
.__init__() takes three arguments, but we gave it four. That makes sense!
We supplied four arguments when creating our new object, but what
.__init__() method is it even checking against? In theory, we just inherited two, right? Well, if we move back into
employees.py, notice that I inherited from
Secretary and then
It looks like it’s trying to use the
.__init__() method, which takes only a
weekly_salary, instead of the two arguments required by the
HourlyEmployee. Let’s switch these and see what happens.
Okay. Now, it says that we’re missing the
weekly_salary parameter. It’s checking against a different
.__init__() method now—the one for the
03:51 This seems kind of unpredictable, but luckily for us, there’s a way to see what it’s doing. It’s called the MRO, or method resolution order. Formally speaking, the MRO is a set of rules that defines the search path that Python will use when searching for the right method to use in cases of inheritance.
04:15 This search path is like an ordered list of classes, and every class has its own MRO. In this video, you’ll see how it’s used to demystify multiple inheritance, but it’s actually used in single inheritance too—although, then, it’s kind of trivial.
You’ll also see how this list of classes is used by the
super() function. We can view the MRO of any class with its special
.__mro__ attribute. To do that,
I’m going to open a new interactive shell, I’ll grab the class with
from employees import TemporarySecretary, and now I’ll write
And when I press Enter, you see we get a listing of classes. This is the method resolution order for the
TemporarySecretary class. When we call any method on
.__init__(), this is the order in which it will be searched for.
It looks like the
.__init__() method is being searched for in the following order:
SalaryEmployee, and finally,
As soon as Python finds an
.__init__() method in one of these classes that matches the arguments we’ve passed in—aka the method’s signature—it will use that
05:49 The problem is it’s not finding one, and so it’s giving us an exception.
05:55 Let me explain what’s going on under the hood.
TemporarySecretary defines no
.__init__() method itself, so it needs to use one that it inherits. The MRO says that searching order should always be left-to-right, children before parents, and so when it can’t find an
.__init__() method inside of
TemporarySecretary, Python searches
If we look at the definition for
HourlyEmployee, it looks like it has a matching signature for the
.__init__() method, but that method calls
super(), which tells the MRO to keep searching the list starting with the next class.
A common misconception is that
super() calls the parent of the current class—in this case,
Employee. That’s simply not true. It just calls the next class in the current MRO list.
In this case, the next class it looks in is
Secretary, but if we look at that class, it looks like
Secretary doesn’t define an
.__init__() method, so it searches its parent,
SalaryEmployee. Here, the method signature for the
.__init__() method doesn’t match what we’ve passed in.
It takes three arguments, but we’ve supplied four—or really, four and five, if you include
self. And that is why we are getting this exception. In order to bypass the MRO,
I’m first going to switch the order of inheritance for
Now, I’m going to create an
.__init__() method that accepts all of the arguments it needs to be instantiated as an
HourlyEmployee. In essence, by overriding the
.__init__() method of the parents and not using the
super() function inside it, we are bypassing the MRO. Instead of
super(), I’ll call the parent class I want directly, which looks like this:
TemporarySecretary will be instantiated using the
HourlyEmployee constructor, but it will still work like a
Secretary since it still inherits that method from the
Our object will be instantiated fine, but when the
PayrollSystem tries to call
.calculate_payroll() on a
TemporarySecretary object, we’re going to get an exception telling us that we haven’t supplied a
That is because
TemporarySecretary doesn’t define a
.calculate_payroll() method, so naturally, Python searches its parents according to the MRO. The next class is
SalaryEmployee, which defines a
.calculate_payroll() method, but it requires a
.weekly_salary attribute and we don’t have that with this
TemporarySecretary since we only initialized it with
Luckily, this is a quick fix. All I’m going to do is define a
.calculate_payroll() method in the
TemporarySecretary class. And when that is called, I’ll tell it to return the
Let’s move over to
program.py and see how this works.
TemporarySecretary is working like a
Secretary and being paid like an
HourlyEmployee. Thank goodness that’s over.
09:42 What you just witnessed is called the diamond problem. The diamond problem occurs when a class inherits from two or more classes, each of which inherits from a single common ancestor.
When this happens, the method resolution order is used to determine what order to search parent classes in. But, as you saw, this can get pretty messy and the only way we could fix it was with a sort of band-aid patch on our
TemporarySecretary class—and even then, we had to be careful. When you see a diamond, it’s typically time to rethink the design of your software.
10:25 Ideally, if you plan out as much of your project as you can ahead of time, you can avoid this problem altogether. The next two videos are going to cover two fundamental questions.
10:38 The first one is “How does Python determine the method resolution order?” and the second is “How do we redesign our project to utilize multiple inheritance, but without the diamond problem?” The next video regarding the MRO is optional.
10:56 It’s not very important that you understand the algorithm behind it but I think it’s pretty cool, so I included it as sort of a bonus. If you’re not interested in that, you can skip to the following video in the course, where I show you how to redesign this project.
Java avoids large bottles of Tylenol by prohibiting multiple inheritance, though does allow “implements” of multiple interfaces. So, the search for a method goes linearly through an explicit single chain of child-parent inheritance and method overrides, or quickly ends at an interface method you’ve explicitly contracted to “implement(s)” in your class (though maybe might be a class you intend to parent … I think Austin mentioned the “abstract” concept back there somewhere). I’m not expert, but I think that’s more or less right … and probably same or similar in C# (I’ve only done a little C#, and long ago).
I needed to watch this video like 5 times and type out the following to understand something about the super() function.
When you called TemporarySecretary(HourlyEmployee,Secretary) at around 3:40 you state the init is being searched for via the following mro path to match the 4 positional arguments passed in:
- TemporarySecretary (no
__init__so move to HourlyEmployee)
- HourlyEmployee (a match with the parameters but because super() is called, it will skip to the next in the mro which is Secretary not the Parent of HourlyEmployee which is Employee)
- Secretary (no
__init__so move to SalaryEmployee)
- SalaryEmployee (
__init__method doesn’t match the 4 positional argument as it has 3). IT CALLS THE SUPER() SO IT WILL MOVE TO EMPLOYEE
- Employee (
__init__method doesn’t match the 4 positional arguments as it has 3 so it would move to object to check).
- Object (Object doesn’t have 4 positional arguments so it fails).
I just want to confirm that super() is called at the SalaryEmployee level like at the HourlyEmployee level and it’s checking the Employee level but it’s init doesn’t match so it fails and checks the parent object.
This is great material by the way and well presented. It’s just taking me multiple goes to wrap my head around the intricacies. Anyone else…just stick with this! It makes sense.
TemporarySecretary() calls –> HourlyEmployee’s init() which matches our TemporarySecretary’s init() with 4 arguments.
HourlyEmployee’s init() calls –>
super().__init__() –> which in turn invokes Secretary class’ init() and since our Secretary class’ init() just takes a single argument(and we passed 4) thereby causing a TypeError
I want to try to answer dwalsh’s question slowly and methodically. Hopefully I will not go astray in the process.
When the author called TemporarySecretary(HourlyEmployee,Secretary) at around 3:40 you state the init is being searched for via the following mro path to match the 4 positional arguments passed in …
First let’s repeat the beginning of the class declaration in this particular inheritance configuration:
class TemporarySecretary(HourlyEmployee, Secretary):
Then let’s show the instantiation of that class:
temporary_secretary = employees.TemporarySecretary(5, 'Robin Williams', 40, 9)
And as a final preparation for discussion, let’s outline the MRO for TemporarySecretary in this particular inheritance configuration:
- <class ‘employees.TemporarySecretary’>
- <class ‘employees.HourlyEmployee’>
- <class ‘employees.Secretary’>
- <class ‘employees.SalaryEmployee’>
- <class ‘employees.Employee’>
- <class ‘object’>
When we search for an
__init__ method, it is important to realize that Python isn’t going to jump over an
__init__ method in the MRO chain that doesn’t match the calling signature. Python looks for the first
__init__ it can find. As soon as Python finds an
__init__, it compares the signatures between the caller and the method. If those signatures don’t match, an exception will be thrown immediately. No further searching along the MRO chain takes place.
With this in mind, when our initial search occurs for an
__init__ method, we first look in TemporarySecretary, and as you noted, because no
__init__ was found, we moved along the MRO chain to look in HourlyEmployee. Here we find an
__init__ method that also happens to match our calling signature:
def __init__(self, id, name, hours_worked, hour_rate): super().__init__(id, name) self.hours_worked = hours_worked self.hour_rate = hour_rate
It’s important to emphasize that we stop searching for an
__init__ method here regardless of whether the signature matches. If the signature had not matched, Python would have immediately thrown an exception and halted execution. But because it does match, we go on to execute that
__init__ method with the corresponding arguments.
In the process of executing that method, we immediately come to a new call to super:
This call commences a new search for an
__init__ method. The signature that we will attempt to match against is also new. We now need an init with only 2 positional arguments (or 3 if you count the implicit self argument). But once again, as soon as any
__init__ is found, the search stops. If the signature matches, we proceed to execute. If it does not, an exception is thrown.
It is also important to note here that we will follow the original TemporarySecretary class MRO in our new search for an
__init__ method. Successive calls to super follow the original MRO. Because we are currently in HourlyEmployee, the next class in the chain is Secretary. As you noted, Secretary has no
__init__. So we then move along to SalaryEmployee, which looks like the following:
class SalaryEmployee(Employee): def __init__(self, id, name, weekly_salary): super().__init__(id, name) self.weekly_salary = weekly_salary
SalaryEmployee has an
__init__ but its signature does not match the new caller. The caller supplies id and name, but the SalaryEmployee also requires weekly_salary. In this case, we have too few arguments. Once again, Python stops searching as soon as it finds an
__init__ of any signature. Because the signatures do not match, an exception is immediately thrown:
__init__() missing 1 required positional argument: ‘weekly_salary’
We never reach Employee or object in our search for an
09:08 Luckily, this is a quick fix. All I’m going to do is define a
.calculate_payroll() method in the
TemporarySecretary class. And when that is called, I’ll tell it to return the
HourlyEmployee‘s init method, why
self is used, on the other hand, I do not see
@agupt039 When you call a method on an object, then Python will implicitly pass the reference to that object as the first argument, which is customarily called
self. However, you can also call that same method through the class name and pass the reference to a specific object yourself:
secretary = TemporarySecretary(42, "Anna", 80, 7.25) # Method bound to the "secretary" instance: secretary.calculate_payroll() # Unbound class method: HourlyEmployee.calculate_payroll(secretary)
The method in the first call is bound to the
secretary instance, so Python knows which object to call it upon. The other one is unbound, so you need to provide an object yourself.
The latter way of calling a method can be helpful in the context of inheritance. For example, it lets you delegate a call to another method specified in one of your base classes, like in the video:
def calculate_payroll(self): HourlyEmployee.calculate_payroll(self)
That was the only way of doing delegation until the
super() function was introduced in Python 2.2. However, the original syntax of the function was a little repetitive because it required both the class and instance parameters to be passed:
class TemporarySecretary(...): def calculate_payroll(self): super(TemporarySecretary, self).calculate_payroll()
Note that you had to pass the child class instead of the parent one! While you can still use this syntax, the current one lets you write the same without repeating yourself:
class TemporarySecretary(...): def calculate_payroll(self): super().calculate_payroll()
However, there is a subtle difference between calling
super(cls, self) and through a base class name like before. It manifests itself when you’re dealing with multiple inheritance, i.e., when your child class has more than one parent. In such a case, there could potentially be a conflict between identically named methods in your base classes. How would Python know which one to call if you used
super() without pinpointing an exact parent class?
To avoid this ambiguity, which is known as the diamond inheritance problem, you can rely on the old syntax. But, it turns out there’s an algorithm called C3 linearization that helps
super() sort this out. You can also inspect or even change the default order of your base classes using method resolution order (MRO).
Hi, just a quck question. Is it possible to trace MRO from Jupyter Notebook? Thanks!
@alazejha I don’t see why not. After all, it’s just a class attribute:
>>> class Dad: ... pass ... >>> class Mom: ... pass ... >>> class Child(Mom, Dad): ... pass ... >>> Child.__mro__ (<class '__main__.Child'>, <class '__main__.Mom'>, <class '__main__.Dad'>, <class 'object'>)
On the other hand, if you wanted to introspect the MRO based on an instance, then just do this:
>>> instance = Child() >>> instance.__class__.__mro__ (<class '__main__.Child'>, <class '__main__.Mom'>, <class '__main__.Dad'>, <class 'object'>)
Why do we need to both call
HourlyEmployee.__init__ AND the
def __init__ constructor on TemporarySecretary? Doesn’t the init method override it already?
class TemporarySecretary(Secretary, HourlyEmployee): def __init__(self, id, name, hours_worked, hour_rate): HourlyEmployee.__init__(self, id, name, hours_worked, hour_rate)
@chasemthomas TL;DR It has to do with multiple inheritance. The two parent classes have incompatible initializer signatures, which is why you must intervene by providing one of your own.
When you implement a custom
.__init__() method in your subclass, you effectively take control over the object construction process. In most cases, you’ll just call
super() to rely on the method resolution order (MRO), which determines your parent class. Alternatively, you can cherry-pick your superclass’ initializers and call them manually, which used to be the old-school way of doing inheritance in Python. Today, it usually indicates that your type hierarchy doesn’t reflect the reality anymore, hence the need for a little bit of hacking. You can avoid such problems by limiting the use of inheritance in favor of composition.
Thanks @Saul for clearly explaining and clearing out that doubt. I had the same question as the one raised by @dwalsh.
Thank @Bartosz Zaczyńsk had the same question for self. Well explained, Much appreciated.
Become a Member to join the conversation.
bvdburg on April 15, 2020
Which languages support MRO and which ones don’t? I am especially interested in python vs c# vs java vs php.