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

super() and the Inheritance Hierarchy

In the previous lesson, you covered objects and inheritance. In this lesson, you’ll dive deeper into method overriding and see how to use super() to access overridden parent methods:

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

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

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

    def what_am_i(self):
        return 'Rectangle'

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

    def what_am_i(self):
        return 'Square'

class Cube(Square):
    def surface_area(self):
        face_area = self.area()
        return face_area * 6

    def volume(self):
        face_area = super().area()
        return face_area * self.length

    def what_am_i(self):
        return 'Cube'

    def family_tree(self):
        return self.what_am_i() + ' child of ' + super(Cube, self).what_am_i() 

00:00 In the previous lesson, I showed you how to use object inheritance in Python. In this lesson, I’m going to show you how to use super() to access methods in the parent objects in a inheritance hierarchy. If you’re coding along with me, I’m still using shapes.py for my code. First off, I’m going to add Cube to shapes.py.

00:21 Just like Square, Cube only requires a length to initialize, and because I’m inheriting from Square, I don’t have to redefine .__init__(). I can just use Square’s.

00:32 Cube has two methods that aren’t in its parent Square. The first is .surface_area(), and second is .volume().

00:40 Both of these use the .area() function from the parent, but I’m showing two different ways of accomplishing the same thing. In .surface_area(), self.area() is called. Python looks for a method called .area() inside of Cube.

00:55 When it doesn’t find it, it will go into Square looking for the same thing. It won’t find it in Square either. If you’ll remember, .area() is defined in Rectangle. When it finds it in Rectangle, it returns the result, and then we multiply that by 6 to get our surface area. .volume() is doing a very similar thing. In this case, though, instead of looking in the current object, it looks at the super object, which means it looks in Square for the .area().

01:23 It still doesn’t find it. It goes up from there to Rectangle and then finds it there and gives us our face_area for the volume. And now if we open up the REPL, we can import the Cube like before, create the Cube with a length of 3,

01:39 call the .surface_area(),

01:41 call the .volume(), and we get our expected results.

01:46 So, let’s review how methods are called on objects in Python. First off, Python looks at the current object to see if a method with the name called exists. If so, great, it calls it.

01:58 If it doesn’t, it goes up to the parent of that object to see if there’s a method with that same name. It keeps doing this until it either finds it and calls it, or it runs out of inheritance chain. If it runs out of inheritance chain, it throws an AttributeError. To show you this in action, I’m going to open up shapes.py and modify the Rectangle shape.

02:20 I’m adding a method called .what_am_i() that does nothing but return a simple string with the word 'Rectangle' in it. Inside of the REPL, I’m going to import a Rectangle and create it, and call .what_am_i().

02:33 It returns the string 'Rectangle'. And what happens if you do the same with Square? Well, I didn’t define anything for Square, so it’s going to call Rectangle’s .what_am_i().

02:44 To change this, the code inside of Square will have to change as well. In order to have Square return a more sensical result, I’ve overridden its .what_am_i() method. I’ve then done the same for Cube.

02:56 Now from the REPL, if I create a Rectangle, nothing’s changed for it—it still reports 'Rectangle'. But when square is called, because there is a .what_am_i() method on the Square object itself, the overridden method gets called. Likewise for Cube, the overridden Cube method gets called and now the results look like what you might expect. Let’s say from inside of a Cube object, you wanted to access the parent Square’s .what_am_i() method. super() is what would allow you to do that.

03:27 super() gives you access to the parent object and the methods within. It can be called in a couple of different ways—straight off without any parameters, but also with two parameters. With the two parameters, it takes a class and an object. In this case, you don’t even have to be inside of the object for that to work.

03:46 If you are inside of a class, super() without any parameters is really just a shortcut for super(my_class) and the self object. Let’s see it in practice by looking at the Cube object.

04:00 Import Cube, create it, call its .what_am_i(), you’ll notice 'Cube'. We’ve seen this before. Now, if I call super() passing in the Cube class and the cube object for .what_am_i(),

04:14 Python goes up to the parent of Cube, which is the Square, and calls its method. Likewise, if we pass it in with the Square class and the cube object, it goes to the parent of Square and returns the result from that.

04:33 This is how super() allows you to get at any of the overridden methods in the inheritance hierarchy. Let’s look at this as an example inside of a method in the Cube class. This new method .family_tree() returns a string, which reports “What I am, and what am I a child of.” self returns the .what_am_i() for the Cube, and super() in this case will return the Square class’s .what_am_i(). You’ll notice that I’m using the short-form version of super() here, not having to specify a Cube class and self, ‘cause I’m inside of an object’s method.

05:12 Let’s create the cube. When .family_tree() is called, 'Cube child of Square' is reported, calling the .what_am_i() of Cube and super().what_am_i() of Square. So far, all of our examples have been single inheritance. In the next lesson, I’ll show you how to use multiple inheritance and the complexities that arise from that.

Avatar image for Brandon

Brandon on Jan. 29, 2020

Thanks for video. One problem, though. I keep getting an AttributeError when calling cube.volume(): type object ‘super’ has no attribute ‘area’ The cube instance of Cube has the property “area”, but it’s throwing an error when it goes looking for it in the parent(s). When I replace face_area = super().area() with face_area = self.area(), cube.volume() works fine. Any thoughts?

Avatar image for Brandon

Brandon on Jan. 29, 2020

It appears the issue may be with the import into the iPython terminal. When I run the code in the Python Interactive Window in VS Code it’s fine, but calling cube.volume() after importing via the terminal (into iPython) throws the error. Oh well, no worries. Thanks!

Avatar image for Mark

Mark on June 15, 2020

Hey Everyone, am sorry if this question is stupid. I am a little struggling, when i import the Parent class all is well but if i import the child class i get this area

from shapes import Rectangle, Square, Cube
>>> cu = Cube(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/real_python_project/int/shapes.py", line 15, in __init__
    super().__init__(length, length)  # accessing the parent method intialisation
TypeError: __init__() takes 2 positional arguments but 3 were given
Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on June 16, 2020

Hi UgandanGuy,

I’m only guessing as I can’t see your code, but if you inherited from Rectangle instead of Square, you might see this message. Rectangle takes two arguments (which means 3 because of “self”), whereas Square only takes one (which means 2 because of “self”).

Double check you have:

class Cube(Square): . . .

…ct

Avatar image for Thomas J Foolery

Thomas J Foolery on June 23, 2020

Thanks Christopher. I liked the video. I was working along side you but not copying verbatim. My little toy looks like this:

# in shapes.py
class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

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

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

    def __str__(self):
        return "rectangle with length = {} and width = {}".format(self.length, self.width)

class Square(Rectangle):
    def __init__(self, length):
        super().__init__(length, length)

    def __str__(self):
        return "square with side-length = {}".format(self.length)


rect = Rectangle(2,4)
print("Rectangle of length {} and width {} has area {} and perimiter {}".format(rect.length, rect.width, rect.area(), rect.perimeter()))

sq = Square(6)
print("Square with sides = {} has area {} and perimeter {}".format(sq.length, sq.area(), sq.perimeter))

print(rect)
print(sq)

The output looks like this:

Rectangle of length 2 and width 4 has area 8 and perimiter 12 Square with sides = 6 has area 36 and perimeter <bound method Rectangle.perimeter of <main.Square object at 0x102e32940>> rectangle with length = 2 and width = 4 square with side-length = 6

Process finished with exit code 0

It seems the perimeter method doesn’t get inherited quite as expected. I’m using PyCharm with Python 3.6. Any thoughts?

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on June 23, 2020

Hi Thomas,

You’ve missed the function call brackets to perimeter. The output you’re seeing is Python telling you about the function rather than calling the function.

Change your line to this:

print("Square with sides = {} has area {} and perimeter {}".format(sq.length, sq.area(), sq.perimeter()))

And you should be good to go.

Avatar image for davevikram

davevikram on June 23, 2020

Hello Chris,

I was following the tutorial and everything was fine until i created Cube class and I can’t get past this error. I even copied the code from above and still continue to get this error.

ImportError: cannot import name ‘Cube’

from shapes import Cube Traceback (most recent call last): File “<stdin>”, line 1, in <module> ImportError: cannot import name ‘Cube’

Please advise.

Thanks, dave
Avatar image for Dan B

Dan B on Nov. 13, 2020

davevikram are you in the REPL? import doesn’t actually re-import until you quit and start again (simplest method).

Avatar image for Dan B

Dan B on Nov. 13, 2020

At 4:44 you say, # super() is a shortcut for super(Cube, self), but I think in this context you mean super(Square, self)?

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Nov. 13, 2020

Hi Dan,

First off, no, I’m not using the REPL. I’ve cut and paste code from the REPL into a slide. You are correct that the base REPL does not allow reimports, so if you need to get at it again you will have to restart.

The comment in the sample code:

# super() is a shortcut for super(Cube, self)

is actually correct. The call to super() is a shortcut to the call of super with the current class. In the example the Cube is the current class. The result of this call is Square’s method. Think of super() as being short for “parent of”. In this case we’re looking for “parent of Cube”, which is Square. A call to the shortcut “super()” is short for “parent of self’s class”.

Avatar image for Konstantinos

Konstantinos on Oct. 5, 2022

Hi, I am trying to understand what is the proper way to call class-level attributes of a parent class from a method in the child class.

In the following example I see that I can use Shape.color, Rectangle.color, super().color to get the color attribute from the parent class in the get_color method of the subclass. I was wondering which one is the most valid call or whether they are equivalent?

class Shape:
    color = "RED"

    def __init__(self, length):
        self.length = length


class Rectangle(Shape):

    def __init__(self, length):
        super(Rectangle, self).__init__(length)

    def get_color(self):
        # return Shape.color
        # return Rectangle.color
        return super().color

Thanks

Avatar image for Christopher Trudeau

Christopher Trudeau RP Team on Oct. 5, 2022

Hi Konstantinos,

The idea behind object inheritance is that the child gets everything from the parent. The only reason to use super is if you’re trying to get at something in the parent not in the child. This only happens if you’re overriding.

You seldom see super used with attributes, it is almost always used with methods where the parent has a different implementation than the child. The most common use when overriding __init__ in the child object and wanting to call the parent’s method of the same name.

You can use super with attributes, for example if both Shape and Rectangle declared color. But in practice, if you’re changing those kinds of constants you probably wanted an instance variable rather than a class level one. I’m sure there are exceptions out there, I just don’t see it used very often in code.

In the case of your get_color method, personally, I’d most likely reference it as self.color.

Hope that helps.

Become a Member to join the conversation.