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.