Join us and get access to thousands of tutorials and a community of expert Pythonistas.
This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.
Multiple Inheritance in PythonPython supports inheritance from multiple classes. In this lesson, you’ll see:
super()
to call methods inherited from multiple parentsA class can inherit from multiple parents. For example, you could build a class representing a 3D shape by inheriting from two 2D shapes:
The Method Resolution Order (MRO) determines where Python looks for a method when there is a hierarchy of classes. Using super()
accesses the next class in the MRO:
Join us and get access to thousands of tutorials and a community of expert Pythonistas.
Already a member? Sign-In
The full lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.
Already a member? Sign-In
00:00 This is the third of three lessons on inheritance in Python and the use of super()
to access methods in parent hierarchy. In this lesson, I’ll be talking about multiple inheritance.
00:12 Multiple inheritance is the process of inheriting from multiple classes into your new base class. In order to do that, I want to add a new base shape called Triangle
. Once again, I’m adding this to shapes.py
.
00:25 There’s nothing in Triangle
you haven’t seen before. The base
and the height
of the triangle are passed in in the constructor. I’ve added an .area()
method, which has the area formula for a triangle—half times the base times the height—and once again, I’ve got a .what_am_i()
method so I can show you how the inheritance mechanisms work.
00:44 So, let’s inherit from Triangle
. The RightPyramid
inherits from Triangle
and Square
—multiple inheritance. In case your geometry is as rusty as mine, a right pyramid is one with a square base and four triangles sloping up to its point. In order to define a RightPyramid
, you need a length
for the base and a slant_height
.
01:05 The slant height is the height of the triangle that is slanted to make the side of the pyramid. Like our other shapes, I’ve also defined a .what_am_i()
so we can look at the relationships.
01:14 Let’s look at the REPL. Import the RightPyramid
,
01:19 create the rightpyramid
, and let’s look at the .what_am_i()
. super()
returns the parent, but in the case of multiple inheritance, it returns the first parent. Because Triangle
was defined before Square
in the list of the RightPyramid
, the super()
method calls the .what_am_i()
of the Triangle
.
01:39 The .__class__
attribute of the RightPyramid
, not surprisingly, says RightPyramid
, and then you may recall the .__class__
attribute also has a .__bases__
attribute that can show you the inheritance. In the case of RightPyramid
, we’re inheriting from Triangle
and Square
.
01:55 One more thing that I’d like to show you is the .__mro__
. This is the method resolution order. This is the order in which Python looks through the inheritance structure.
02:07 In this case, it starts out looking at the RightPyramid
, then goes up the inheritance structure to look at the Triangle
. Triangle
has no parents, so it moves on to the next thing in the multiple inheritance hierarchy, which is the Square
. Square
’s parent is the Rectangle
. And then finally, because Rectangle
has no parents, it reaches the top, the <class 'object'>
.
02:27 All objects in Python inherit from the object
object. You may recall from the first lesson, the results of the dir()
function had a lot of double underscore (__
) methods in it.
02:37 Those double underscore methods are all defined inside of this object
object, which everything in Python inherits from. The method resolution order dictates to Python how to look up a named method when it is called. In single inheritance, this is usually fairly simple.
02:53 It is the path of the inheritance hierarchy. In multiple inheritance, this can get complicated. It’s quite possible that your inheriting classes could have methods of the same name. The MRO dictates which of them gets called. These name clashes can cause problems and can make your code a little confusing.
03:10 There’s different ways of handling it. First off, you could rewrite your code so there are no name clashes. Take our .area()
method on Square
and Triangle
, and rename them to be the .square_area()
and the .triangle_area()
. Now name resolution isn’t necessary, because the name is unique to the class.
03:27 You can use the inheritance declaration itself to specify the order in which things are looked up. This is where MRO shines. You have to be careful though, because the result between RightPyramid
, Triangle
, then Square
versus RightPyramid
, Square
, Triangle
will change which .area()
method gets called. Finally, you can also specify the call itself directly. Using the class object, you can call the .area()
method and pass in the object itself.
03:56 This is the most explicit way. If someone else is reading your code, they won’t have to remember what the MRO is; they’ll know you’re using the Square.area()
method. To illustrate how the MRO works with multiple inheritance, I’ve created a new file called chain.py
. Inside of it, I’m creating five different classes, A
, B
, X
, Forward
, and Backward
.
04:16 Each one of the classes simply has a constructor that prints out that you’re inside of its constructor and then calls the super().__init__()
. At first blush, this might seem weird. If you look at the constructor for A
, there is no parent to A
, but we’re still calling super()
.
04:32 You’ll see why in a second. Let’s pull out our trusty REPL and create a couple of objects. First off, forward
.
04:40 The first thing Forward()
does is call the Forward
constructor. The Forward
constructor prints 'Forward'
and then calls super()
. The super of Forward
is B
.
04:51 So next, we see B
. Going into the B
class, the super()
for this is called, the super of B
is A
, so 'A'
gets printed.
05:00 Then we go into A
, and super()
of A
is called. There is no super for A
. Up until now, I’ve always talked about the parent. In single inheritance, this is true. In multiple inheritance, it’s a little bit of a white lie.
05:14 What is actually happening is the next object in the MRO is what is called. So in this case, the super for A
is X
. If you go back to Forward
, Forward
inherits from B
, then inherits from X
.
05:28 So B
chains to A
, A
chains to X
when super()
is called. To contrast this, let’s look at Backward
. Backward
’s constructor called super()
, that’s X
. X
also has no parent, but super of X
will be the next object in the MRO, which in this case is B
. B()
calls super()
, that inherits from A
.
05:51 A()
calls super()
, and we’re done the chain. As you can see, specifying the order of inheritance of B
and then X
, or X
versus B
changes what methods get called in what order. Let’s take the complexity of this chaining and apply it to our shapes code. First off, I’ve got my RightPyramid
. Notice that I’ve taken the inheritance order and changed it from before.
06:16 Currently, the RightPyramid
is inheriting from Square
first and then Triangle
. In addition to the potential challenge of method names clashing in inheritance, you also have the problem of how to actually construct them.
06:28 In the RightPyramid
you have a base
and a slant_height
, but it’s based on a Square
and a Triangle
, whose constructor attributes aren’t base
and slant_height
—they’re length
and height
. This can cause problems when you start to inherit. One way around this is to use kwargs
(keyword args).
06:45 If you haven’t seen this before, Python supports the ability to pass in a dictionary with the double asterisk (**
) in front of it. This dictionary forms name-value pairs as arguments for a method.
06:57 This allows you to pass in attributes that, in this case, RightPyramid
is going to ignore.
07:03 I’ve taken the base
and the slant_height
and assigned them as before, added a "height"
and "length"
keyword argument, and then I pass it into super()
.
07:12 What that means is when the super()
gets called for the constructor for Square
, you now have all of the arguments there: base
, slant_height
, height
, and length
. Square()
can take the length
that it needs and pass that into its own super()
. base
, slant_height
, and height
are all still there inside of kwargs
, but Square
doesn’t use them. Square()
calls its own super()
. Rectangle
pulls out the length
and width
that it’s interested in inside of kwargs
, calls its own super()
—which of course, there’s no base class for Rectangle
, so now we’re going into RightPyramid
’s inheritance MRO, calling the Triangle
constructor, and the Triangle()
gets the base
and the height
.
07:53 This allows us to have all of the attributes passed in for all of these objects. This works, but it isn’t the easiest code to read. Multiple inheritance solves certain kinds of problems, but it can also make code rather difficult to follow along.
08:08 You need to be careful how you use this tool. There are often ways of constructing the code that are easier to read that achieve the same end result. The complexities of multiple inheritance make programmers wary sometimes about when to use it.
08:22 One of the easiest ways of making sure you don’t have problems is to create classes that don’t have name clashes and are as independent as possible.
08:30 A pattern that’s very common in a lot of frameworks like Django and Flask is something called a mixin. A mixin is an independent class that gets pulled in in the inheritance hierarchy but is not going to impact anything that inherits it. An example of this is the SurfaceAreaMixin
.
08:48 The SurfaceAreaMixin
provides a single method, which is .surface_area()
, and has no expectation about construction. All it requires is that somewhere in the class that is using it, there’s an attribute called .surfaces
. Here’s the RightPyramid
modified to use the SurfaceAreaMixin
.
09:05 SurfaceAreaMixin
is inherited into the RightPyramid
and .surfaces
is defined—a list of the different surfaces for the RightPyramid
.
09:15 The .surface_area()
method can now be called calculating the surface area without the SurfaceAreaMixin
having to understand what the shape is that is being inherited from. Here’s another example with the Cube
. Same idea—SurfaceAreaMixin
is inherited from, and the six surfaces of the Cube
are defined inside of .surfaces
.
09:37 The independence of the SurfaceAreaMixin
means there aren’t any name clash complexities, but you still have the power of inheritance.
09:46 Let’s see this in the REPL. We construct our Cube
, call our .surface_area()
, and get our result.
Course Contents
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4