Multiple Inheritance
00:00 In the previous lesson, I introduced you to inheritance and multi-level hierarchies. In this lesson, I’ll show you how a class could have multiple parents.
00:10 Python supports multiple inheritance, meaning a class can be a child of many parents. When you do this, you get a union of all the things in every parent you inherit from.
00:20
Let’s go look at some code. Okay, more classes about vehicles and including flying vehicles. You just sort of run out of examples after a while. At the top here, I have a parent class having .make
and .model
attributes and .start()
and .stop()
methods. Let me scroll down a bit.
00:41
Here is a Car
. The Car
is a Vehicle
, and as it doesn’t override anything, it gains the .__init__()
, attributes, and .start()
and .stop()
methods from Vehicle
.
00:52
It then declares its own additional method called .drive()
. Scrolling a bit more …
01:01
AirCraft
is another Vehicle
. Similar to Car
, it gets its parent’s members and adds its own method. And here it’s time to enter the future: a flying car.
01:12
The syntax for inheriting from multiple classes is to comma-separate the list of classes in the declaration. The FlyingCar
is both a Car
and an AirCraft
, which of course also makes it a Vehicle
through its grandparents.
01:26 Let’s create some objects.
01:39
I’ve created a FlyingCar
and then called .start()
. Remember that method was declared in Vehicle
, the grandparent, and
01:51
as FlyingCar
is a Car
, it can drive.
01:56
And because it’s an AirCraft
, it can fly. The same rules around super()
apply here as well. Everything from the previous lesson can be used in conjunction with multiple class inheritance.
02:10
Multiple inheritance introduces a problem though you can get conflicts. Consider this diagram representing the classes you just saw. What happens if I extend the .start()
method in Car
, changing its behavior?
02:23
Now I have what is called the diamond problem, named after the shape of the class diagram. Which .start()
does FlyingCar
get? The one from Car
or the one from AirCraft
, which AirCraft
inherited from the grandparent, Vehicle
?
02:39 Different languages deal with this problem differently. Let’s talk about how Python does it. Python’s answer is called the method resolution order, or MRO to its friends. When you look for a member on a class or object, Python looks for it in the following order. First, it looks at the current class, then at the leftmost superclass.
03:02
That’s the first one listed in the inheritance declaration. That was Car
in my example. After that, it moves in order along the inheritance declaration. So after Car
, it would check in AirCraft
. If it still hasn’t found what you asked for, it then moves up to the grandparents, following the same inheritance declaration order until finally it gets to the ancestor of all objects. In Python, the object
class. That name’s not confusing.
03:31
If you ever wondered where .__init__()
, .__str__()
, and similar methods are defined if you don’t define them in your class, well, they’re in the object
class.
03:40 All classes in Python inherit from it automatically. So, the short version of the MRO: the order of the class declaration statement is the order Python looks for things. Let’s go resolve some method orders.
03:55
Pardon the generic class names here, but sometimes an abstract concept makes it clearer. This file has four classes: A
, B
, C
, and D
. A
is the grandparent, B
inherits from A
, C
inherits from A
, and D
inherits from B
and C
. A
, B
, and C
all have a method creatively named .method()
. As D
doesn’t override anything.
04:20
The MRO is what determines what happens. Before I show the result, take a second and determine what will happen when I call .method()
on the D
object. Okay, let’s see if you were right.
04:34
Importing D
… instantiating … and calling D
‘S .method()
method. As D
doesn’t override the .method()
method, the MRO gets used.
04:47
The first class that D
inherits from is B
, so B
’s method is what was resolved. Python even includes a handy attribute that shows you the MRO for a class in case you aren’t sure.
05:01
.__mr0__
shows D
, B
, C
, A
, and then the ancestor of all: object
. If you ever run into a tricky inheritance problem and you’re not sure where something is coming from, .__mro__
can be helpful.
05:17
A mixin is a special name for a class that doesn’t have any attributes. You inherit from a mixin in order to add its methods to your class. This often serves the same purpose as that oh-so-common util.py
file everybody has where you put miscellaneous functions.
05:34 It’s the object-oriented version of the same. You build a mixin with generally useful methods, then include it in any class where you need those methods.
05:45 Mixins are never instantiated directly, and you’ll see them a lot in frameworks. For example, in Django, you might use one that adds database query features, and you’d mix it in with your database models.
05:59
A quick tangent before I show you a mixin. Python stores the writable members of classes and objects in an internal dictionary. This dictionary is named .__dict__
.
06:11
The built-in vars()
function displays the contents of that dictionary on your object. I kind of glossed over something quickly at the top of this slide.
06:20 Just what do I mean by writable? Well, things that you can change. For an instance object, that is its attributes. For a class, that’s pretty much everything.
06:33 The next couple sentences are a bit of a deep dive behind the scenes, and you really don’t have to worry about it when you’re writing classes, but methods are functions bound to a class using a reference.
06:45
When you invoke a method on an instance, it’s invoking the corresponding bound function on the class, passing in the instance. Because it’s an object, you can actually modify the reference on the class and point it to a different function. You really shouldn’t unless you’re trying to do something fancy, but that’s actually how it works. Why do I bring all this up? Well, if you look at the writable things on a class, that includes the methods as well as any class attributes. For the purpose of the mixin I’m about to show you, the important thing you have to remember is that .__dict__
contains the attributes of an object. There’s an exception to all this, but I’ll leave that to a later lesson.
07:29
This file contains a mixin that does JSON serialization. The JSONMixin
is a class like any other. It has methods and no attributes. The .to_json()
method uses the json
module’s dumps()
function to turn a dictionary into a string containing JSON.
07:47
What dictionary would that be? Well, the .__dict__
contains all the writable members of the object, which are its attributes, which is what I want to serialize.
07:57
And then to use it, the Circle
class simply inherits from the mixin. That means the radius and the color attributes can be turned into JSON.
08:08
Likewise, the Rectangle
also inherits from the mixin, gaining the same powers. Let’s use this. Importing …
08:21
creating a circle. Here’s Circle
’s .__dict__
… and the same stuff but using vars()
to get at it.
08:35
Remember when I spoke about that writable stuff? Ah, for giggles, let’s look at the class’s .__dict__
.
08:42
This is a bit more complex than at the instance level. It contains two special attributes showing the module and docstring, and the .__init__()
method as a function reference. Like I said, more an implementation detail.
08:55 Unless you’re getting really tricky with your classes, you really don’t have to worry about this. All right, and what you came to see: the mixin.
09:04
The .to_json()
method serializes the attributes of the Circle
object, and of course, the reason you do mixins is code reuse. Here’s the Rectangle
…
09:24 Coming up, I’ll lift the covers a little bit more and show you some internals of classes.
Become a Member to join the conversation.