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

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands 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 your subtitle preferences 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 refer to our video player troubleshooting guide for assistance.

Inheritance Best Practices

00:00 You’ve now learned how to write what is fundamentally the same software project using both inheritance and composition. The rest of this course will be about learning when to use each one, and more importantly, when not to use them.

00:18 We’ll start with inheritance. First off, it’s a good idea to try to follow the Liskov substitution principle when you can. If you remember, this states that if some interface requires a specific type of object, you can substitute in a class that is a child of that type so long as it properly inherits the interface it needs.

00:46 We use this in our employee management program—both the productivity and payroll systems required a list of employees to operate on—but instead of just giving them Employee objects we gave them subclasses of Employee, like Manager or Secretary.

01:07 Additionally, the Python language requires that we make exception types inherit from BaseException, but we inherited from Exception, which is okay because Exception itself inherits from BaseException.

01:24 When you have two classes and you’re trying to determine whether or not you should inherit one from the other, think about the is a relationship that inheritance models. In general, you want to use inheritance only if the relationship works in one direction—that is, A is a B but B is not an A.

01:50 If you can justify the relationship in both directions, you should avoid making one inherit from the other. To understand why, let’s take a look at a code demo.

02:02 Let’s say we are writing a program to calculate the areas of various shapes. I’m writing this in a new module called rectangle_square_demo.

02:14 There are two shapes we need to be able to handle: squares and rectangles. Squares and rectangles are pretty similar. A square is just a rectangle but with equal sides.

02:28 A rectangle, on the other hand, does not have to have sides of equal length. At the same time, you could argue that a rectangle is just a square with uneven lengths.

02:42 It sounds like this relationship works either way, so we probably shouldn’t use inheritance. To show you what would happen if we do, I’m going to create a Rectangle class and then a Square class that inherits from Rectangle. I’ll make the Rectangle have instance attributes for the length and the height.

03:05 I’m using a leading underscore (_) to show that we should not access or modify these attributes from outside of the Rectangle class.

03:15 In order to calculate the area, I’ll create a new method called .area(). This will simply return the ._length times the ._height.

03:26 I’m going to add this @property decorator to the method so that we can access it as if it were an attribute and not a method. That just means that we’ll call the method without parentheses.

03:41 Now, I’m going to create a new class called Square, which will inherit from Rectangle. This class will have a different interface for its requirements.

03:53 It will accept only a side_size and then it will initialize its parent constructor, passing in that size for both the length and the height.

04:06 That looks like it should be good. Let’s try this out by instantiating the classes. I’ll create a rectangle object that will store a 2 by 4 Rectangle.

04:19 This should give us an area of 8. To check that I’ll say assert rectangle.area == 8. The assert keyword acts almost as a conditional.

04:35 If the Boolean statement evaluates to True, execution continues to the next line.

04:41 If it’s False, execution stops and an AssertionError is raised. Also, notice how we didn’t call the .area() method with parentheses.

04:52 It almost looks like it’s an attribute. That’s because we marked that method as a property. We can use a property for this method because we aren’t passing any data to the .area() method. We’re just calling it, so it calculates the area based on the instance attributes already set in the object. Next, I’ll create a square of length 2.

05:18 Just like before, we want to assert that the area of this square is 4.

05:25 If all this checks out I’ll print "OK :)".

05:31 It looks like everything is okay so far. Right now, Square just acts as a specialized Rectangle. But now what happens if we have to add support for resizing the rectangle after it’s been created? That doesn’t sound too hard.

05:50 All we have to do is create a method called .resize(), which will change the instance attributes of the Rectangle to some new values.

06:01 I’ll add this new method to our list of assertions.

06:08 All right, that seems to work fine. What about resizing a square? The Square class inherited the .resize() method from the Rectangle class.

06:20 I’ll give it some dimensions that are not equal.

06:29 And now we see that it’s working, unfortunately. We’ve just created a Square object and transformed it into what’s really a rectangle, but it’s still of type Square; now it’s just an invalid square. There are ways that we can fix this, but it’s going to be awkward any way we do it.

06:50 We could override the .resize() method in the Square class, ignoring the height parameter, but then we’d have a bunch of Rectangle objects where only some of them are resized with two parameters—the rectangle—and others—the squares—are resized with one. The Square and the Rectangle require different interfaces because their .resize() methods require a different number of arguments.

07:18 This justifies not inheriting one from the other.

mojkol on Dec. 18, 2021

First, thank you for the great tutorial! Let me ask a quick question. I just don’t understand why the Square class can’t inherit the Rectangle? You said, because resize() method requires two parameters for the Rectangle class and it requires one parameter for the Square class. What confuses me is that, the same goes for the init() method. init() of Rectangle accepts two parameters and the init() of Square accepts one parameter! So, why this fact is not a problem for init(), but it is a problem for resize()?! Thanks!

Bartosz Zaczyński RP Team on Jan. 3, 2022

@mojkol The rectangle-square inheritance is a classic example of the Liskov Substitution Principle (the “L” in SOLID) violation, leading to subtle bugs. In a nutshell, code that expects rectangles should work correctly with instances of rectangle’s subclasses without knowing the difference. Since squares exhibit different behaviors and properties than rectangles, you may be forced to check the type information at runtime, which breaks polymorphism. There’s a great lecture available for free on YouTube by Uncle Bob about the SOLID principles, which goes into more detail about the rectangle-square problem.

Become a Member to join the conversation.