So, this ELI5 has been sitting in my drafts folder for a while. Not sure how that happened there! Woops! After some delay, here is the final EL15 for now – how to subclass an object!
Making a Truck
Way back I ELI5’d objects in python by explaining how to make a car. There are other types of vehicles that will likely behave similarly to a car but would need specific methods. One way to handle this is by using subclassing, where you can make a new class that inherits behaviour from a parent class and adds its own.
In this example I am going to make a truck, aka a car that carries stuff.
class Truck(Car):
def __init__(self, inputModel, inputCargo):
super().__init__(inputModel)
self.cargo = inputCargo
So, lets go over what I did line by line.
First, when defining the object I added a (Car) term. This tells python that Truck is a subclass of Car.
I don’t need the mileage = 0 line because the Car object already has that term – I only need to add in the new aspects of the Truck class.
In the init method I have a term super().__init__(inputModel). This is calling the init method for the parent class and passing the argument inputModel into it.
Then I have the attribute unique to the truck self.cargo which I set equal to the other argument.
Truck will have all the same methods of Car, shown below
--- myTruck = Truck("Hauler", "beets")
--- print(myTruck)
"Hello, I am a Hauler and I have travelled 0 miles"
--- myTruck.vrooom(10)
--- print(myTruck)
"Hello, I am a Hauler and I have travelled 10 miles"
--- myTruck.cargo
"beets"
Doing Truck Things
In addition to having new attributes, a subclass can have new methods.
class Truck(Car):
def __init__(self, inputModel, inputCargo):
super().__init__(inputModel)
self.cargo = inputCargo
def deliver(self):
self.cargo = None
Now Truck will have a method to make deliveries, which Car does not have.
--- myTruck = Truck("Hauler", "beets")
--- myTruck.cargo
"beets"
--- myTruck.deliver()
--- myTruck.cargo
None
We can also “override” methods from the parent class in a subclass. To do this we just define a method with the same name as before.
class Truck(Car):
def __init__(self, inputModel, inputCargo):
super().__init__(inputModel)
self.cargo = inputCargo
def deliver(self):
self.cargo = None
def __str__(self):
output = f"Hello, I am a {self.model}, I have travelled {self.mileage} miles"
if self.cargo is None:
output += " and I am not carrying anything"
else:
output += f" and I am carrying {self.cargo}"
return output
Now, the Truck objects will have a different statement when print is used while the print statement for Car will be unchanged.
--- myTruck = Truck("Hauler", "beets")
--- print(myTruck)
"Hello, I am a Hauler, I have travelled 0 miles and I am carrying beets"
--- myTruck.vrooom(10)
--- myTruck.deliver()
--- print(myTruck)
"Hello, I am a Hauler, I have travelled 10 miles and I am not carrying anything"
Other Ways to Make Your Truck
In this example, putting the car’s input statements in the truck’s init wasn’t a big deal. However, in practice you’ll want to avoid this. For example, lets say in future car is updated to add a year_manufactured attribute, this update would break your truck! So, it is usually a good idea to make your input statement a bit more general, which you can do by using *args and **kwargs. Remember those?
There are two ways you can do this, we’ll start with my preferred way which is to specify the new parameters in the input method’s argument:
class Truck(Car):
def __init__(self, *args, inputCargo, **kwargs):
self.cargo = inputCargo
super().__init__(*args, **kwargs)
In this method, I start the input statement with an *args parameter, end it with a **kwargs parameter and put my new parameters in the middle. This will send all the *args and **kwargs to the parent class to be handled as needed while also communicating clearly what parameters I need for my own object.
Another way is the more general approach, which is to leave the init statement completely abstract and manipulate the **kwargs parameter to remove the keyword arguments you need.
class Truck(Car):
def __init__(self, *args, **kwargs):
self.cargo = **kwargs.pop('inputCargo')
super().__init__(*args, **kwargs)
In this method you use the pop method in dictionaries to look for an inputCargo keyword argument, remove it from the **kwargs dictionary and then send it along with *args up to the parent.
Conclusion
So there you have it – you now know how to subclass your objects as well as multiple ways to handle the init statement for your subclasses. I currently don’t have more ELI5s planned, however as my experience in webscraping at scrapinghub grows I hope to write some general tips on using the scrapy webscraping package.