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:
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.
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!
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
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
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?
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.
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
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).
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)
?
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”.
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
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.
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()
withface_area = self.area()
, cube.volume() works fine. Any thoughts?