Locked learning resources

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

Unlock This Lesson

Locked learning resources

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

Unlock This Lesson

Multiple Inheritance in Python

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:

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

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

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

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

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

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

Avatar image for Richard Morris

Richard Morris on Jan. 14, 2020

Superbly motivated, organized, and paced. Most appreciated

Avatar image for Alex

Alex on Jan. 14, 2020

Thank you,especially for the Mixins example.

Avatar image for Anonymous

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.

Avatar image for rhuang

rhuang on Jan. 30, 2020

Great tutorial! Thanks!

Avatar image for ajp4

ajp4 on Feb. 2, 2020

Best explanation I have seen. Thank you!

Avatar image for Dima

Dima 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!

Avatar image for Ryan Cook

Ryan Cook 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!!

Avatar image for Erikton Konomi

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!

Avatar image for ricardoaparicio92

ricardoaparicio92 on April 18, 2020

I’m lost!

Avatar image for Ricky White

Ricky White RP Team on April 19, 2020

@richardoaparicio92 What are you struggling with?

Avatar image for Liz Schley

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.

Avatar image for Liz Schley

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.

Avatar image for graham17

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

Avatar image for Christopher Trudeau

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

Avatar image for samsku1986

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)
Avatar image for Vijay Alagappan

Vijay Alagappan on June 20, 2020

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

Avatar image for Nathan L

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.

Avatar image for Christopher Trudeau

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.

Avatar image for nj8456

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 ?

Avatar image for Christopher Trudeau

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.

Avatar image for Omkar

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?

Avatar image for Christopher Trudeau

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.

Avatar image for Omkar

Omkar on Sept. 4, 2020

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

Avatar image for James

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.

Avatar image for Christopher Trudeau

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.

Avatar image for Ghani

Ghani on Oct. 7, 2020

Very clear and well-explained!

Avatar image for Saul

Saul on Nov. 7, 2020

Excellent material! I have a small quibble and some hopefully useful observations. Please correct me if I go astray. At around the 7:18 mark, it is noted that the Square class constructor receives base, slant_height, height, and length as arguments. But slant_height is not actually included among those arguments. It was captured as a positional parameter in the constructor for RightPyramid, along with base. The reason base itself gets passed up the chain is because we specifically re-submit base in the super().__init__(base=base, **kwargs) method call. We don’t re-submit slant_height because it isn’t used by any superclasses.

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)

In fact, if we had included slant_height, an exception would later result when the super().__init__(**kwargs) call to object ultimately occurred:

TypeError: object.init() takes no parameters

Using super().__init__(**kwargs) in this way to chain across multiple inheritance hierarchies (by following the MRO) means that we cannot have any extraneous or unused keyword arguments. They have to be captured by parameters with the same name in a constructor somewhere in the chain. That’s the only way they get consumed and removed from the kwargs dictionary.

Another thing: I was a bit troubled at first by the assignment to the kwargs dictionary in the constructor for RightPyramid. I couldn’t figure out why we would pass along height and length as arguments with these assignment statements but then pass along base as an argument in the super().__init__ call. Well it seems that these approaches are equivalent. We could have also done either of the following just as well:

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
        kwargs["base"] = base
        super().__init__(**kwargs)
class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height, **kwargs):
        self.base = base
        self.slant_height = slant_height
        super().__init__(height=slant_height,
                         length=base,
                         base=base, 
                         **kwargs)

Again, please let me know if I am missing something. Thanks!

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Nov. 7, 2020

Hi Saul,

Thanks for your comments.

At around the 7:18 mark, it is noted that the Square class constructor receives base, slant_height, height, and length as arguments. But slant_height is not actually included among those arguments.

I believe what I was attempting convey was that slant_height gets passed in but as the height argument. You are correct that slant_height is not kept as a named argument.

Your are also correct that you have to be careful with these things and what gets consumed. You only have to make sure they all get used if there is a class somewhere with an empty init(), if your constructors always have the *args, **kwargs arguments you should be ok (he writes nervously without testing :) ). That being said, this is why I tend to prefer to use inheritance for Mixins only. These kinds of subtleties can definitely cause you weird problems both at compile and runtime.

I was a bit troubled at first by the assignment to the kwargs dictionary … Well it seems that these approaches are equivalent.

Yep, you’re right. It has been too long since I wrote that code, I don’t remember why I did it that way. I suspect it had something to do with how it evolved out of earlier versions. Either approach works.

Happy coding!

Avatar image for anindo78

anindo78 on Nov. 27, 2020

Great tutorial on inheritance!

Avatar image for rikhuygen

rikhuygen on Feb. 7, 2021

Great tutorial, and very clearly explained.

There is one thing I’m not completely convinced and that is the Mixin example. The method surface_area() in the SurfaceAreaMixin class uses the self.surfaces which you defined in the sub-classes. That sounds a little weird to me. How would I know from the Mixin class which instance variables I must define so the method would not crash with an AttributeError. Is that best practice? Should that be learned from the documentation or by code inspection? Shouldn’t the self.surfaces be at least declared in an __init__() method of the Mixin class?

Thanks, Rik

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Feb. 8, 2021

Hi @rikhuygen,

Yep, this is one of the challenges of trying to teach mixins and object oriented lessons. The really good uses of this kind of tech tend to require more background information.

The whole “using shapes” thing to explain OO inheritance is tried and true but has its limitations. Even if I was going to write a graphic interface that used shapes, I likely wouldn’t go full OO, or if I did, the base class would be much lighter than is used in typical explanations.

The best place for things like Mixins are where you have a lot of independence and are trying to add a feature that isn’t quite specific.

For example, I recently wrote something in Django that required a special kind of identifier. The identifier was a little more complicated than just a field and needed some methods. All of this was put inside of a mixin so that any Django object that needed this kind of identifier would just mix that in. The problem with teaching with this kind of example is then you have to explain what Django is, and how it models objects and how that relates to fields and a whole bunch of other stuff.

To your specific question, the amount of code saved by using this mixin probably doesn’t warrant the hiding of the implementation that happens the way it was done. In the real world I probably wouldn’t have coded it that way, but then I wouldn’t have had a mixin example.

Avatar image for rikhuygen

rikhuygen on Nov. 9, 2021

Fair enough, thanks for your answer. (sorry for my late reply)

Avatar image for Alex

Alex on Jan. 6, 2023

Hi, Christoph, i’ve enjoyed the tutorial. Could you please answer to my following 2 questions (kind of similar to @Saul, but not entirely i guess):

–1. Is a bad practice to create instance variables(instead of passing it through kwargs) in the init of the Child class, if they are consumed only in the Parent classes, like in the first part of the following example:

class RightPyramid( Triangle,Square):
    def __init__(self, base, slant_height):#no kwargs
        self.base = base
        self.slant_height = slant_height
        self.width = self.base #added width in self
        self.length = self.width #added length in self
        super().__init__(base=self.base,height = self.slant_height) #no kwargs


class RightPyramid( Triangle,Square):
    def __init__(self, base, slant_height,**kwargs): #added kwargs
        self.base = base
        self.slant_height = slant_height
        kwargs['length'] = self.base #added k/v for length
        super().__init__(base=self.base,height = self.slant_height,**kwargs) #added kwargs

–2. What would be the difference between passing the variable in kwargs or passing it separately through the keyword argument?

class RightPyramid( Triangle,Square):
    def __init__(self, base, slant_height,**kwargs):
        self.base = base
        self.slant_height = slant_height
        #kwargs['length'] = self.base # pass by kwargs
        super().__init__(base=self.base,height = self.slant_height,length=self.base,**kwargs) # pass by extra keyword
Avatar image for Alex

Alex on Jan. 6, 2023

Sorry, just a later note, i guess having mixings is a nice feature, but one disadvantage(please correct me if wrong) is that it is polluting the instance with the unrelated instance variables of the RightPyramid class for example:

class RightPyramid(Square, Triangle, SurfaceAreaMixin):
    def __init__(self, base, slant_height):
        self.base = base
        self.slant_height = slant_height
        self.height = slant_height #not needed directly by the class
        self.length = base #not needed directly by the class
        self.width = base #not needed directly by the class

        self.surfaces = [Square, Triangle, Triangle, Triangle, Triangle]
Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Jan. 7, 2023

Hi Alex,

Use of **kwargs is the more general case, and I might do it if I’m not trying to expose just what the parent is doing. It is kind of a catch-all. If you’re building structures where you’re inheriting directly, there is nothing wrong with using named arguments instead.

One advantage of **kwargs is pass-through to the next level. The best example I can think of is on database objects in Django: they have a save() method that takes a number of parameters. You can override a save() in a child class and only use the arguments that you need, but if some other coder comes along and needs the arguments you didn’t declare, they’re stuck. So best practice is to use **kwargs for those things you’re not using and pass them all to the parent call.

It really just comes down to a design decision though.

As for your question about polluted classes, yeah, in this case that is an unfortunate side-effect. Shapes are a good analogy for teaching inheritance concepts, but are just an analogy. The best examples of mixins usually require deeper understanding of other systems than is helpful when teaching.

My most commonly used mixin is again in the Django space. I like to have date/time stamps on all database tables that track when a row was created and last updated. It can be useful when debugging production problems. As such, the mixin has these fields in it and just gets added to any data class I create. As these fields and any associated methods are independent of the classes mixing them in, there is no pollution problem. Teaching with this example is problematic though, because you can’t run the code unless you’ve got all the Django stuff setup, and so you’re left with having to use mediocre analogies.

With a lot of this stuff it comes down to practice. Even with practice there are plenty of times where I got bit by a design decision I’ve made and then have to come back in and change it to something else later. Welcome to the fun of coding :)

Avatar image for Andras

Andras on March 22, 2024

Hi Chris,

I enjoy your videos very much. At around 8:00, when you show all classes and their __init__ methods, RightPyramid is calling Square’s __init__ with a base keyword, which it doesn’t have. Shouldn’t the last line read:

super().__init__(length=base, **kwargs) ?

Thanks

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on March 23, 2024

Hi Andras,

Multiple inheritance is tricky.

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)

The RightPyramid is inheriting from both Square and Triangle. In this code I’m using base, height, and length in super().__init(). With the base being specified explicitly, and the height and length in kwargs.

You’re right that the Square has no base, but because the arguments are named, and because Square accepts **kwargs: base gets passed in but ignored.

The length value that Square accepts is set in the kwargs dictionary inside RightPyramid, and in fact is a copy of base.

If Square and Triangle did not have **kwargs in their constructors, this code would not work. Since they do have **kwargs, you can pass anything you want in as a named argument, but their respective __init__ code only uses the named arguments they’re interested in. Everything else gets ignored.

Hope this helped.

Avatar image for Andras

Andras on March 25, 2024

Hi Chris,

Thanks for the answer. I think most of my confusion came from how **kwargs are handled in general; it was not directly related to multiple inheritance. My previous proposal would have resulted in TypeError: Square.__init__() got multiple values for argument 'length' if I ran it, which I did not (shame on me). Now that I understand **kwargs better, let me ask one more minor thing: when you write the RightPyramid constructor, you know the direct parent is Square, so you could pass lenght=base directly and pass on everything else in kwargs that other parents (further up the chain) will need, like this:

class RightPyramid(Square, Triangle):
    def __init__(self, base, slant_height, **kwargs):
        self.base = base
        self.slant_height = slant_height
        kwargs["height"] = slant_height
        kwargs["base"] = base  # note "base" key!!!

        super().__init__(length=base, **kwargs)  # keyword length=base
        #super().__init__(base, **kwargs)  # positional arg would work too

Was there any specific reason why you “hid” length in kwargs and passed base explicitly? With your approach kwargs inside Square.__init__ becomes a different dict than the one you passed in. This is not a problem, I understand it now. This is the magic Python gives us automatically with kwargs functionality (select what’s needed, ignore everything else). Just asking if there was any trick or design pattern you followed that I should keep in mind too when writing similar code.

regards, Andras

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on March 26, 2024

Hi Andras,

Glad you figured it out. Given the course is four years old, I’m not sure I can say exactly what was going through my head when I coded it.

I could argue the more generic use of keyword values is slightly safer in the case where you end up changing the inheritance order, but I’m not sure how important that is. If I was doing a code review, I’m not sure I would be too worried about this case.

The secret reality of all this stuff is that I very seldom use multiple inheritance. It is far more common in other languages (and sometimes far more necessary). Although it is a core computer science concept, and a useful tool in your toolbag, it is only one of a choice of approaches. When I do use multiple inheritance, it tends to be for mixins, which by definition have argument-less constructors, so this particular case we’re discussing doesn’t come up.

If you enjoy the OO aspect of Python and want to understand a bit more, one of the more recent courses does an in depth dive. It has three parts, covering:

There is some overlap with this course, but far more depth. The third course talks about some of the rules of object-oriented design and touches on some of the whens/whys of usage.

Happy coding!

Avatar image for Andras

Andras on March 26, 2024

Thank you very much, Chris! I just finished that three-part course few days ago. I enjoyed it very much, especially the last part on design. I have many years C++ experience and I feel like I am still looking for the same design principles in Python that I used to work with in C++. It would be nice if you could put together some courses comparing Python vs C++ or Java, and explain what’s the pythonic approach to the most common problems in OOP that are usually tackled with design patterns in C++/Java (singleton, visitor pattern, etc.)

Keep up the good work!

cheers, Andras

Become a Member to join the conversation.