Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set the default subtitles language in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

Multiple Inheritance

Give Feedback

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.

00:30 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 SalaryEmployee.

00:47 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 HourlyEmployee.

00:58 We could achieve this with single inheritance—for example, by inheriting from Secretaryso it’s tracked using the inherited .work() method—and then creating our own .calculate_payroll() method to calculate payroll as an hourly employee.

01:15 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 employees.py.

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.

01:48 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!

01:58 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.

02:10 I’ll give them an id and a name,

02:15 and because they’re going to be billed hourly, they’ll need to have an .hours_worked and .hour_rate attribute.

02:27 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.

02:41 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!

02:58 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 HourlyEmployee.

03:19 It looks like it’s trying to use the Secretary’s .__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.

03:40 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 SalaryEmployee.

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.

04:34 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,

04:46 I’m going to open a new interactive shell, I’ll grab the class with from employees import TemporarySecretary, and now I’ll write TemporarySecretary.__mro__ (dunder mro).

05:03 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 TemporarySecretary, including .__init__(), this is the order in which it will be searched for.

05:23 It looks like the .__init__() method is being searched for in the following order: TemporarySecretary, HourlyEmployee, Secretary, SalaryEmployee, and finally, Employee.

05:37 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 .__init__() method.

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.

05:59 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 HourlyEmployee next.

06:22 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.

06:39 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.

06:53 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.

07:13 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,

07:28 I’m first going to switch the order of inheritance for TemporarySecretary.

07:35 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: HourlyEmployee.__init__().

08:08 Now, the TemporarySecretary will be instantiated using the HourlyEmployee constructor, but it will still work like a Secretary since it still inherits that method from the Secretary class.

08:22 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 weekly_salary.

08:37 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 .hours_worked and .hour_rate attributes.

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 .calculate_payroll() method.

09:26 Let’s move over to program.py and see how this works.

09:32 Great. Our 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.

09:57 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.

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.

Zarata on April 15, 2020

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).

dwalsh on May 27, 2020

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.

dwalsh on May 27, 2020

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.

ayushm796 on Aug. 26, 2020

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

Saul on Nov. 9, 2020

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:

  1. <class ‘employees.TemporarySecretary’>
  2. <class ‘employees.HourlyEmployee’>
  3. <class ‘employees.Secretary’>
  4. <class ‘employees.SalaryEmployee’>
  5. <class ‘employees.Employee’>
  6. <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:

super().__init__(id, name)

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:

TypeError: __init__() missing 1 required positional argument: ‘weekly_salary’

We never reach Employee or object in our search for an __init__ method.

Become a Member to join the conversation.