Inheritance Syntax:
Let's make some tweaks to our class to make it a bit more generic: We now have a Vehicle
class that looks very similar to our Car
class.
In our __init__
method, we've added a capacity
parameter, that way a vehicle object can keep track of how many max passengers they can have.
This means that we can create Vehicles
that have a capacity of 3 passengers, or 1, or 6, or 20! They will all use the same logic.
How about the types of vehicles
we have? Cars and BigCars and MotorBikes? It seems like we could just add another attribute to the Vehicle
class to track that, something like:
class Vehicle:
def __init__(self, license, capacity, vehicle_type):
self.license = license
self.capacity = capacity
self.available = True
self.passengers = []
self.vehicle_type = vehicle_type
But what happens if we want to add functionality to some types of vehicles, but not others? Say for example that we let our users order food in our apps. We're happy to let cars and bikes deliver that order, but we don't want to use bigcars for that.
def order(self):
if self.vehicle_type == 'car' or self.vehicle_type == 'bike':
# place the order
else:
# ignore the order
With every new functionality though, we'd have to ask ourselves: Does this apply to all the vehicle types we support? which ones allow it, which ones don't?
Things can get even more annoying if we introduce a new type of vehicle. Imagine if we also want to support Bikes
. We'd have to go through all our methods and see if a Bike
should or shouldn't be able to perform that action.
There has to be a better way! and there is. Let's talk about Inheritance
Inheritance
Inheritance is one of the pillars of Object-Oriented Programming. It enables us to create a new class that already has all the data and functionality of another class. In this case, we may want to create a Car
class that inherits from the Vehicle
class.
We leave our Vehicle
class unchanged, but we create two new classes: Car
and MotorBike
.
You will notice that those class definitions are very light: No __init__
, only an __str__
method we added to help us demonstrate differences between the various classes.
Here we say that Car
is a subclass of Vehicle
. We can also say that Car
is a children class of Vehicle
, or that Car
extends Vehicle
Similarly, we say that Vehicle
is a parent class to Car
.
As you can tell however, the two objects we create for our new classes are able to add passengers and drop them off. That is because a Car
object is a Vehicle
object, and can do everything a Vehicle
can.
This is very helpful! if we make a change to the Vehicle
class, all its subclasses will be able to benefit, while we also keep our ability to customize each subclass.
Let's focus on the syntax once again:
def my_subclass(ParentClass):
# define custom code for the subclass
# All methods of your parent are by default methods of the subclass
Let's look into this code in Python Tutor to get a sense for what's going on in-memory. To simplify our analysis, we only look at Vehicle
and Car
:
After Step 1 and Step 2, we see 2 classes defined in our global frame. Note how it's clear to Python that the Car
class extends the Vehicle
class.
In Step 3, as we create a new Car
object, you can see that we call the __init__
method of the Vehicle
class. This makes sense because we did not define a specific __init__
for Car
, and a Car
is a Vehicle
so we can rely on that constructor.
In Step 10, we want to print our object. Step 11 takes us to the __str__
method we defined for Car
. If we hadn't defined this method, we would look for the __str__
method of the parent class, etc.
In Step 14, we want to use the request
method. In Step 15 we see the code jump into the Vehicle
class' method definitions and executing the code there!
Conclusion:
We can use inheritance to save ourselves from repeating a lot of similar code.
Inheritance is ideal in scenarios where we have many related models to implement, particularly if model_A
is a kind of model_B
. We can define model_B
, and have model_A
inherit methods and data from it.
We can customize our subclasses with their own methods, and we can also call methods from their parent classes. This also applies for grandparent classes, or great-grandparent classes! you can keep this going as much as needed.
What happens in scenarios where we don't want to inherit everything the parent class has to offer? Let's find out in the next section.