Homework for Week 14
After this week you can
- Define a subclass of a class
- Override methods
- Create a string representation of an object
Inheritance
Last week we took a closer look at how to create classes and define constructors, variables, and methods. So far, however, we lack the knowledge of how to effectively create classes that are very similar in terms of content but have small differences. In this chapter, we will explore how to create child and parent classes, what is inheritance and polymorphism, and how to output the object information more conveniently.
Watch the lecture video on inheritance:
Slides in English
Textbook in English
Subclass and superclass
When creating different classes, we may encounter a situation where we want to create a class that is very similar to another class but has specific characteristics. Do we create all classes separately and define separate methods in each class? To simplify this work, we can use subclasses and superclasses. A subclass is a class that inherits all its variables and methods from the superclass.
To create subclasses, we first need to define a superclass. Below we define a class Dog whose instances have the following properties: name, age, and favorite food. We also add a method voice that returns the sound of the dog barking.
class Dog: def __init__(self, name, age, favorite_food): self.name = name self.age = age self.favorite_food = favorite_food def voice(self): return "Whoah Whoah"
After defining the superclass, we can now create subclasses. For this, we define a new class with a unique name, put parentheses after the class name, and put the name of the parent class inside. In our example, the superclass is Dog, so we need to add it in the parentheses. If no new elements are to be added to the body of the subclass, the keyword pass
must be written as its body.
Let's create two subclasses of the superclass Dog with names of two different dog breeds, for example KingCharlesCavalierSpaniel and GermanShepherd.
class KingCharlesCavalierSpaniel(Dog): pass class GermanShepherd(Dog): pass
Now let's create some instances of the subclasses and see what functionality they inherit from the superclass.
carlos = KingCharlesCavalierSpaniel("Carlos", 4, "sausage") ralph = GermanShepherd("Ralph", 2, "curd") print(carlos.voice()) print(carlos.name) print(ralph.voice()) print(ralph.favorite_food)
Output
Whoah Whoah
Carlos
Whoah Whoah
curd
Inheritance and polymorphism
At the end of the previous example, we encountered inheritance for the first time. With the help of inheritance, the subclass can use the variables and methods of the superclass, i.e., they are carried over from the superclass to the subclass. For a class to inherit the methods and variables of another class, we specify the name of the superclass in the parentheses after the class name.
Subclasses can use all the methods defined in the superclass, but if necessary, they can also contain new, unique methods or override (i.e., define again) the methods existing in the superclass.
In the dogs' example, we saw that the subclasses inherited the method voice from the superclass, as well as all the variables of the superclass. Now let's try to create new methods in the same subclasses and override a method of the superclass.
class KingCharlesCavalierSpaniel(Dog): def walk(self, length_km): time = length_km / 10 * 60 return round(time) class GermanShepherd(Dog): def voice(self): return "Wuff Wuff" carlos = KingCharlesCavalierSpaniel("Carlos", 4, "sausage") ralph = GermanShepherd("Ralph", 2, "curd") print("Carlos' walk lasted", carlos.walk(10), "minutes.") print("Carlos' voice: ", carlos.voice()) print() print("Ralph's voice: ", ralph.voice())
Output
Carlos' walk lasted 60 minutes.
Carlos' voice: Whoah Whoah
Ralph's voice: Wuff Wuff
We can see that for the instances of the GermanShepherd subclass, the method voice is different from the method voice of the superclass because we overrode it. We also added a walk method to the KingCharlesCavalierSpaniel subclass, which is unique to this subclass: it is not found in the superclass Dog, nor in the second subclass GermanSheepDog.
Overriding the methods of the superclass is called polymorphism. Polymorphism means that a method defined in a superclass is defined anew in a subclass, and therefore an instance of the subclass behaves differently. Polymorphism is useful when we need a method in a superclass to behave differently from a method in a subclass. The overridden method voice in the subclass GermanShepherd is an example of polymorphism.
Accessing the components of superclass
Sometimes it is necessary to call the constructor of the superclass in the subclass. Let's assume here that all Mongrels are to be automatically named Rex. In this case, we can use the constructor of the superclass Dog with the command super() in the Mongrel class:
class Mongrel(Dog): def __init__(self, age, favorite_food): super().__init__("Rex", age, favorite_food) def voice(self): return "Hau-hau!"
In this case, when creating a new GermanShepherd class instance, its name will become Rex.
Analogous principles apply to methods. If a method is overridden in a subclass, the method in the superclass is not directly accessible. To call it, we write the statement super() before the method's name. In general, super() returns an object of the superclass, together with all its instance variables and methods.
Method __str__
After creating an instance of a class, it may be necessary to display information about the instance of the created class. We are trying to display the instances of the KingCharlesCavalierSpaniel and GermanSheepdog classes.
carlos = KingCharlesCavalierSpaniel("Carlos", 4, "sausage") ralph = GermanShepherd("Ralph", 2, "curd") print(carlos) print(ralph)
Output
<__main__.KingCharlesCavalierSpaniel object at 0x04090DD0>
<__main__.German Shepherd object at 0x03640DF0>
We see that when printing an instance of the class, a confusing text appears on the screen, as if in an encrypted form. In reality, the type of the instance of the class and its location in the computer's memory are displayed on the screen. But such an output is of no help to us. To output the necessary information about the instances of the class, we can create a method called __str__.
The __str__ method takes care of what is output to the screen when an instance of the class is printed. For this purpose, it would be reasonable to define the method __str__ in the superclass Dog, so that it will be inherited by all subclasses.
class Dog: def __init__(self, name, age, favorite_food): self.name = name self.age = age self.favorite_food = favorite_food def voice(self): return "Whoah Whoah" def __str__(self): return f"{self.name} is {self.age} years old and its favorite food is {self.favorite_food}" carlos = KingCharlesCavalierSpaniel("Carlos", 4, "sausage") ralph = GermanShepherd("Ralph", 2, "curd") print(carlos) print(ralph)
Output
Carlos is 4 years old and its favorite food is sausage
Ralph is 2 years old and its favorite food is curd
Now, when printing an instance of a class, logical information about the instance is displayed on the screen.
To return all the information about the class instance as one string, a Python formatted string was used here. It allows adding variables directly into the sentence without breaking the sentence into pieces. To add variables to a sentence, surround them with curly braces.
Quiz
Solve the quiz on inheritance in Moodle.
Exercises
1. Garage
Create a class Vehicle
whose constructor's parameters are the brand of a vehicle, its price in euros, and its fuel consumption per 100 kilometers. The Vehicle
class must also have a __str__ method that returns all information about the vehicle.
Create the classes Truck
, Car
, and Motorcycle
, which are subclasses of the Vehicle
class. All three classes must have the following methods.
- Method trip_cost, which takes the length of the trip in kilometers as an argument and returns the cost of the trip, rounded to one decimal place. The fuel prices per liter are 1.8 euros for a truck, 1.9 euros for a car, and 1.85 euros for a motorcycle.
- Method horsepower, which outputs the vehicle's horsepower value to the screen. A truck has 500 horsepower, a car 180, and a motorcycle 85.
Create a class called Garage
, whose constructor's parameter is a list of vehicles. The Garage
class also has a method display that outputs the information about the vehicles in the garage. For each vehicle, the method must output the usual information of the vehicle, after that the number of horsepower of the vehicle, and finally, by calling the method trip_cost, how many euros it would cost for this vehicle to travel from Tartu to Tallinn (186 kilometers).
All vehicles to be added to the garage are stored in the file vehicles.txt. On each line, the vehicle type is separated from the vehicle information by a space, a hyphen, and a space (" - "). Vehicle information (brand, price, and fuel consumption) are separated from each other by a comma and a space (", "). Example of the file vehicles.txt:
Truck - Mann, 80000, 30 Motorcycle - Suzuki, 12000, 10.5 Truck - Scania, 72000, 35 Car - Opel, 10000, 7.3 Car - Toyota, 15000, 5.7
In the main program, read the vehicle data from the file vehicles.txt, construct the vehicles and add them to the garage. Then call the display method of the garage.
Sample output
The following vehicles are in the garage:
Mann, price 80000 euros, fuel consumption 30.0 liters per 100 km.
The truck has 500 horsepower.
It costs 100.4 euros to travel from Tartu to Tallinn.
Suzuki, price 12000 euros, fuel consumption 10.5 liters per 100 km.
The motorcycle has 85 horsepower.
It costs 36.1 euros to travel from Tartu to Tallinn.
Scania, price 72000 euros, fuel consumption 35.0 liters per 100 km.
The truck has 500 horsepower.
It costs 117.2 euros to travel from Tartu to Tallinn.
Opel, price 10000 euros, fuel consumption 7.3 liters per 100 km.
The car has 180 horsepower.
It costs 25.8 euros to travel from Tartu to Tallinn.
Toyota, price 15000 euros, fuel consumption 5.7 liters per 100 km.
The car has 180 horsepower.
It costs 20.1 euros to travel from Tartu to Tallinn.
2. Rental of records
Create a class called Record
whose constructor's parameters are the name of the record, the genre, and the year of release. The Record
class must also have a __str__ method that returns all information about the record.
Create classes called Rock
, Pop
, and Classical
, which are all subclasses of the Record
class.
- All classes must have a constructor with the name and release year of the record as parameters. The constructor calls the constructor of the superclass, giving the name of the class as its genre argument. Hint: the super() command gives the object of the superclass.
- All classes must have a cost method, which takes the number of days (integer) as an argument and returns the cost of renting the record for the given time period. Renting rock records costs 2.5 euros per day, renting pop records costs 1.7 euros, and renting classical records costs 4.9 euros per day.
Create another class called RentalShop
, whose constructor's parameter is a list of records. The class also has a borrow method, which first asks how many euros the user has when entering the shop. The method then allows the user to enter the following commands repeatedly.
- L - list all records that are currently available for rental.
- B <name> <number_of_days> - borrow the record with the specified name for the specified number of days. If the record is available and the user has enough money for the rental, renting cost is subtracted from the user's money, and the record is deleted from the list. If the record is available, but there is not enough money, the method prints an appropriate message. If there is no record with such a name in the rental, it also prints a suitable message.
- R <name> <genre> <year> - return the record, i.e., add a record with the specified characteristics to the list of records. It can be assumed that the user enters the data correctly.
- E - exit the rental and terminate the method.
Before each command, the method must print the amount of money the user has.
Add the following three records to the rental and test the "borrow" method:
- "Nevermind", "Rock", 1991
- "Thriller", "Pop", 1982
- "Symphony No. 40", "Classical", 1788
Sample output
How many euros do you have for renting records? 50
------------------------
Rock music rental costs 2.5 euros per day.
Pop music rental costs 1.7 euros per day.
Classical music rental costs 4.9 euros per day.
------------------------
Command descriptions:
L - list the records in the rental
B <name> <number_of_days> - borrow record
R <name> <genre> <year> - return record
E - exit the rental
------------------------
Your balance is 50 euros.
Enter command: L
Nevermind, Rock, 1991
Thriller, Pop, 1982
Symphony No. 40, Classical, 1788
------------------------
Your balance is 50 euros.
Enter command: B Thriller 30
Not enough money for rental!
------------------------
Your balance is 50 euros.
Enter command: B Thriller 25
Thriller borrowed.
------------------------
Your balance is 7.5 euros.
Enter command: R Anima Mea Classical 2021
Anima Mea returned.
------------------------
Your balance is 7.5 euros.
Enter command: L
Nevermind, Rock, 1991
Symphony No. 40, Classical, 1788
Anima Mea, Classical, 2021
------------------------
Your balance is 7.5 euros.
Enter command: E
Submit your solutions
Submit the solutions to the tasks via Moodle as files home1.py and home2.py.