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.
Inheriting Data and Methods
00:00 In the previous lesson, I covered callables and how not all built-in functions are actually functions. This is the last lesson in the section on objects and classes, and I’ll be talking about inheritance. Part of object-oriented programming is class inheritance.
00:16 That’s where you can define a class based on another class, inheriting its data and methods. This gives you code reuse and can also describe hierarchical relations in your class structure.
00:27
You’ve already seen this concept when I covered the type() function creating the Car class based on the more generic Vehicle class.
00:35
A child class can define a method with the same name as its parent class, hiding the parent’s implementation. When you do this, you can use the super() built-in function to access the parent’s method that you hid. Someone may have mentioned to you that everything in Python is an object.
00:51
Not only is that correct, it’s even a little too general. Everything in Python is a very specific object. The object class is the parent of all objects in Python, and there’s a built-in function similar to other data type functions that you’ve seen allowing you to create these kinds of objects specifically.
01:09
Off to the REPL to talk about objects and super(). I’m going to start with the class inheritance equivalent of “Hello, World” in object-oriented programming.
01:42 This is a class defining a rectangle. It has two attributes, the length and width of the rectangle. It also has one method that calculates the area.
01:51 Squares are a specialization of rectangles. If I want to describe a square in a class, I only want to send in one side and use that side as both the length and the width.
02:03
I can do that with inheritance. The parentheses here is how I inherit. The Square class will be a Rectangle class. If I did nothing else, these classes would be essentially equivalent except for their names. But I want to override the .__init__() so that it only takes one argument.
02:25
Now I need to call the parent class’s .__init__(). Seeing as I’ve overridden it, I can’t take advantage of it directly. To access it, I need to use the super() built-in. super() returns a proxy object that contains references to the methods on Rectangle.
02:45
The pattern I’ve used here is pretty common, calling the function and chaining the result to the method that I want, which in this case is the parent rectangle’s .__init__(). I then pass side in as both the length and width, meaning my square, which is also a rectangle, will be properly initialized.
03:02 Let me instantiate this to demonstrate that it worked.
03:07 There’s my square. I constructed it, sending in a single integer, the length of the side.
03:13
Through the use of the parent’s .__init__(), the length and width got set appropriately.
03:21
And since Square inherits from Rectangle, it gets access to the area() method as well. Inheritance can save you a lot of code.
03:30
Seeing as all squares are rectangles, you can get both concepts with only three lines of extra code. Also, any new additions, for example, if I added perimeter to Rectangle, would also cascade down to Square automatically.
03:43
I’ve shown the simplest and most common use of super(), one without arguments. With no arguments, super() looks at the class above.
03:50
If you have a more complicated hierarchy, you can pass arguments to super() to tell it where to look for the method you want.
03:59
ColorSquare inherits from Square, which of course inherits from Rectangle. Here, I’ve defined a new .__init__() that takes a side and a color.
04:11
Since ColorSquare is also a square, I could just use super() on its own. But to prove my point, I’m going to call rectangle’s .__init__().
04:19
I do that by adding arguments to super().
04:28
The first argument to super() is a type, the type of the class where I start looking for the thing I want. I always mess this up. Logically to me, if I wanted rectangle’s .__init__(), I would put Rectangle here, but that’s not how this works.
04:43
I’ll come back to this confusion in a moment. For now, just understand that you need to start at the child before the thing you wish to access. The second argument to super() is a reference to the object that’s being used, which is just self.
04:57 Now that I’ve called the parent, I need to store away the color:
05:02 color, without a “u”. As a Canadian, speaking the Queen’s English, it pains me to type it that way. Like John Waite, I’m missing U. Wow, that was bad.
05:17 And to show that this is a rectangle,
05:25 And of course, my new attribute is there as well. All right, now that you’ve seen that work, let’s go back to the mess.
05:34
This looks kind of black magic-y, doesn’t it? Python’s MRO is the method resolution order. .__mro__ reveals the order that Python looks for methods and attributes in a class hierarchy.
05:48
So for our ColorSquare, when you call the area() method, the MRO says to look for a method of that name in ColorSquare.
05:57
Of course, it doesn’t find it. So then it looks in the next one, which is Square, and it still doesn’t find it. So then it looks in the next one, which is Rectangle, and it finds it there.
06:08
So that’s where it gets called. When you call super() with arguments, what you’re specifying is the point in the MRO to begin searching past.
06:17
By using Square like I did in the example above, I’m actually saying to start looking at the thing after Square, which in our case is Rectangle.
06:26
Not how I would have built it, but that’s the way it is. In my first attempt at this little example, I got an error because when I was calling .__init__() with super(), I passed in Rectangle, and therefore it attempted to use the .__init__() from the object after Rectangle, which is the object class.
06:44
And how’s that for a segue? As I mentioned, everything in Python isn’t just an object, but the object. At the root of all object inheritance is the object class.
06:55
Terminology at this level is messy. You end up with the type type and the object class. Confusing, but the history of computing terminology can be a little convoluted.
07:05
Just like our other class data type built-in functions, the object class can be invoked.
07:12
Now I have an instance of the object class, which when you evaluate is the object object. Well, what did you expect to get from instantiating the object class but the object object?
07:24 Having fun fun yet yet? This is really an underlying mechanism. You shouldn’t use it directly. You can’t even use it as a dummy because the base object doesn’t allow dynamic addition of attributes.
07:38
Like I said, not allowed. The error message does reveal something interesting though. Objects in Python support dynamically adding attributes through the use of a dictionary. That dictionary is called .__dict__, and the base object instances don’t have it.
07:54 Let me show you a quick example.
07:57
.__dict__ on our color square has three attributes in it, the length, the width, and the color.
08:04 But there’s no such thing on our object, which is why you can’t add attributes.
08:11 Although everything in Python is an object, not all of them allow dynamic addition. For example, many of the built-in functions are actually wrappers to the C libraries that are implementing them underneath, which is done for speed and efficiency.
08:25
Those kinds of objects don’t allow you to add attributes. To go along with .__dict__, there’s a companion attribute called .__slots__, which you can use to stop dynamic addition, which gives you more efficient objects.
08:38
I’ll point you at some other content about this in a minute. Just to go full circle, I want to briefly come back to the question of when to use object objects.
08:47
The only example I’ve seen in the field is using an object object as a dummy sentinel. For example, something to use to signal uninitialized values in a cache. Most of the time you can use None for that, but seeing as None might be a valid value in your use case, an object object could be used as a marker instead.
09:07
There’s a recent PEP that is going to add this kind of sentinel into Python directly, which will mean you won’t even need this use case of object object anymore.
09:19
To learn more about super(), take a look at this video course or tutorial. Or, if you’re interested in the mechanics behind dynamic objects, this content on .__dict__ gives you a peek behind the curtain.
09:31
It also briefly covers .__slots__ as well. This was the last lesson in the section on classes and objects. Next up, I’ll talk about scopes in Python.
Become a Member to join the conversation.