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 in Python

Give Feedback

Python supports inheritance from multiple classes. In this lesson, you’ll see:

  • How multiple inheritance works
  • How to use super() to call methods inherited from multiple parents
  • What complexities derive from multiple inheritance
  • How to write a mixin, which is a common use of multiple inheritance

A class can inherit from multiple parents. For example, you could build a class representing a 3D shape by inheriting from two 2D shapes:

class RightPyramid(Triangle, Square):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height

    def what_am_i(self):
        return 'RightPyramid'

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:

class A:
    def __init__(self):
        print('A')
        super().__init__()

class B(A):
    def __init__(self):
        print('B')
        super().__init__()

class X:
    def __init__(self):
        print('X')
        super().__init__()

class Forward(B, X):
    def __init__(self):
        print('Forward')
        super().__init__()

class Backward(X, B):
    def __init__(self):
        print('Backward')
        super().__init__()

If you combine the MRO and the **kwargs feature for specifying name-value pairs during construction, you can write code that passes parameters to parent classes even if they have different names:

class Rectangle:
    def __init__(self, length, width, **kwargs):
        self.length = length
        self.width = width
        super().__init__(**kwargs)

    def area(self):
        return self.length * self.width

    def perimeter(self):
        return 2 * self.length + 2 * self.width

class Square(Rectangle):
    def __init__(self, length, **kwargs):
        super().__init__(length=length, width=length, **kwargs)

class Triangle:
    def __init__(self, base, height, **kwargs):
        self.base = base
        self.height = height
        super().__init__(**kwargs)

    def tri_area(self):
        return 0.5 * self.base * self.height

class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height, **kwargs):
        self.base = base
        self.slant_height = slant_height
        kwargs["height"] = slant_height
        kwargs["length"] = base
        super().__init__(base=base, **kwargs)

    def area(self):
        base_area = super().area()
        perimeter = super().perimeter()
        return 0.5 * perimeter * self.slant_height + base_area

    def area_2(self):
        base_area = super().area()
        triangle_area = super().tri_area()
        return triangle_area * 4 + base_area

Multiple inheritance can get tricky quickly. A simple use case that is common in the field is to write a mixin. A mixin is a class that doesn’t care about its position in the hierarchy, but just provides one or more convenience methods:

class SurfaceAreaMixin:
    def surface_area(self):
        surface_area = 0
        for surface in self.surfaces:
            surface_area += surface.area(self)

        return surface_area

class Cube(Square, SurfaceAreaMixin):
    def __init__(self, length):
        super().__init__(length)
        self.surfaces = [Square, Square, Square, Square, Square, Square]

class RightPyramid(Square, Triangle, SurfaceAreaMixin):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height
        self.height = slant_height
        self.length = base
        self.width = base

        self.surfaces = [Square, Triangle, Triangle, Triangle, Triangle]

Here’s what you get:

>>>
>>> cube = Cube(3)
>>> cube.surface_area()
54

Download

Course Slides (PDF)

896.4 KB

Richard Morris on Jan. 14, 2020

Superbly motivated, organized, and paced. Most appreciated

Alex on Jan. 14, 2020

Thank you,especially for the Mixins example.

Anonymous on Jan. 16, 2020

Yea, I have read the documentation multiple times, but the way this was presented made it extremely digestible and clear - very well put together. Now to watch it 5 more times.

rhuang on Jan. 30, 2020

Great tutorial! Thanks!

ajp4 on Feb. 2, 2020

Best explanation I have seen. Thank you!

SkyFox on March 27, 2020

I came to this tutorial to understand to concept of Mixins. I really happy I got everything in less than 30 minutes. Really appreciate such explanation simplicity!

Anonymous on April 4, 2020

I liked the explanation of the “why” in creating Mix-ins and the potential pitfalls of multiple-inheritance. I love the explanations simplicity and your measured vocal speed. Keep up the Good Work!!

Erikton Konomi on April 12, 2020

Fantastic tutorial! I really enjoyed how concise and to the point all three videos were, with solid examples. The introduction of the mixin pattern however made all the difference in the end. I’ll be decoupling some code at work pretty soon with this :) Thanks!

ricardoaparicio92 on April 18, 2020

I’m lost!

Ricky White RP Team on April 19, 2020

@richardoaparicio92 What are you struggling with?

Liz Schley on April 28, 2020

This is great, so thank you! My purpose was to understand how OOP works in Python, so that I am capable of designing good code when I need to.

Liz Schley on April 28, 2020

My favorite thing was the mro method and using mixins to prevent confusion. Definitely my plan, if possible since confusing code creates bugs that are time-consuming to fix.

graham17 on May 18, 2020

Really useful - a few pennies dropped right here. The only thing I didn’t get was the final cube = SACube(3) I suspect this was an error - I couldn’t find any ref to class SACube anywhere - but maybe I missed it - overall - a great concise tutorial

Christopher Trudeau RP Team on May 19, 2020

Hi graham17,

Good catch. That’s an artifact from an earlier version of the code. We’ll fix it shortly. Thanks for letting us know. …ct

samsku1986 on May 31, 2020

In case of single inheritence why use super() method when we could call the method using class name directly ??. I mean what is the advantage ?

class Rectangle: def init(self,length,width): self.length = length self.width = width

def area(self):
    return (self.length * self.width)

def permiter(self):
    return (2 * self.length + 2 * self.width)

class Square(Rectangle):

def __init__(self,length):
    Rectangle.__init__(self,length,length) >>>>>>>>>>>>>>>>>>>>calling Rectangle,method() directly instead of super
    #super().__init__(length,length)

Vijay Alagappan on June 20, 2020

Crystal clear explanation of concepts such as MRO and Mixins! Thanks a lot.

Nathan L on June 27, 2020

You pointed out with Cube that you could access Rectangle.area() either by self.area() or super().area. What’s best practice? I would imagine best practice is to use self.parent_method unless there is a name clash, as with __init__ in subsequent examples.

Christopher Trudeau RP Team on June 30, 2020

Hi Nathan,

Generally, you use self.method() unless there is a reason to do otherwise. If I’m having to get at the parent, then super(), or if there is some situation where clarity is needed that I’m calling a parent method.

nj8456 on July 6, 2020

Encountered this code in “Creating custom Model Managers in Django” in Django by Example Book.

GIVEN the following code

class PublishedManager(models.Manager):
    def get_queryset(self):
        return (
            super(PublishedManager, self) 
                .get_queryset()
                .filter(status='published')
                )

class Post(models.Model):
    objects = models.Manager()
    published_objects = PublishedManager()
    publish = models.DateTimeField(default=timezone.now)

AND GIVEN objects is the default Model manager for Post,

THEN the code super(PublishedManager, self) is equivalent to calling the default manager Object - objects.get_queryset() from within an instance of the PublishedManager class right ?

Christopher Trudeau RP Team on July 7, 2020

Hi nj8456,

Yes, you’re right. PublishedManager is overriding models.Manager.get_queryset(), so within that override, if you called self.get_queryset() you would get the overridden instance – a recursive call. The use of super() here gets you at the parent’s method.

This is a common pattern in Django models. You do something similar if you want to override a Model’s save() method – call the parent, to do what it would normally do, then run your specialized code.

In the case of PublishedManager.get_queryset(), you want to do a query just like the parent, but this time also apply a filter. The parent is called, then the filter is applied, then the filtered result is returned.

The brackets notation is a short form for doing:

qs = super(Published Manager, self).get_queryset()
return qs.filter(status='published')

Because get_queryset() returns a QuerySet, the:

fn()
.other_fn()
.more_fn()

pattern, chains the results of each of them together, calling the next function on whatever was returned by the previous.

Omkar on Sept. 4, 2020

Since class A or X here are the ones last in the forward and backward classes, we get done with the chain. But does it mean the class object init is also called here when last on the chain?

class A:
    def __init__(self):
        print('A')
        super().__init__()

Last on the chain or if even if this was just a single class calling it’s super() does mean calling object right as it is the parent?

Christopher Trudeau RP Team on Sept. 4, 2020

Hi Omkar,

Yeah, that one’s a bit tricky. On its own A wouldn’t need the super().init() because it’s super is just the base Object class.

Where it becomes important is through the chaining. In the case of the Backward class, A’s super().init() does nothing. In the case of the Forward class, it is why X’s constructor gets called.

super() calls the next class in the MRO.

The chain works like this:

Backward -> X -> B -> A

So A’s call to super().init() does nothing. By contrast though:

Forward -> B -> A -> X

In this case, without A’s super().init(), X’s init method would not get called.

If you are writing classes that are likely to be mixed in or used with inheritance, it is always good practice to call “super().init()” in case other programmers using your class change the order of inheritance.

Omkar on Sept. 4, 2020

Thanks for the crystal clear explanation :) Enjoying being a part of this community!

James on Sept. 8, 2020

I didn’t quite understand this part of the mixin:

class Cube(Square, SurfaceAreaMixin):
    ...
        self.surfaces = [Square, Square, Square, Square, Square, Square]
class RightPyramid(Square, Triangle, SurfaceAreaMixin):
    ...
        self.surfaces = [Square, Triangle, Triangle, Triangle, Triangle]

Why are they with these values? I’m from Brazil and as this video is still without subtitles it was a little difficult for me to understand. Thanks.

Christopher Trudeau RP Team on Sept. 9, 2020

Hi James,

The idea here was to highlight what a Mixin is used for: code that is common across some classes without necessarily having deep knowledge about the class.

The SurvaceAreaMixin.surface_area() method calculates the surface area of a 3D shape by looking for an attribute named “surfaces” and calling the area() method for each of the surfaces, adding them together.

A Cube has six sides, each of which is a square. By setting Cube.surfaces to be six Squares, the mixin’s surface_area() method will correctly calculate the surface area of a Cube.

The RightPyramid is comprised of a square base and four triangular sides. It represents this by setting its “surfaces” attribute to [Square, Triangle, Triangle, Triangle, Triangle].

The mixin knows on an abstract level how to calculate surface area (by calling the area() method on each shape in the “surfaces” attribute), but doesn’t need to know what the surfaces are for the object inheriting the mixin.

The specifics of the 3D shape are described in the implementation of the class (Cube or RightPyramid), but the algorithm for calculating the surface area can be separated out from this into a common mixin class.

Without the mixin, you would need to write separate surface_area() methods for the Cube and RightPyramid. Having it in the mixin means writing and testing it in only one place.

Become a Member to join the conversation.