ELI5 Tutorial: Subclassing Objects in Python

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.

This entry was posted in ELI5, Python, tutorial and tagged , . Bookmark the permalink.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.