Lecture 16 Inheritance

1. Introduction



Every object-oriented programming language would not be worthy to look at or use, if it weren't to support inheritance. Of course, Python supports inheritance, it even supports multiple inheritance. Classes can inherit from other classes. A class can inherit attributes and behaviour methods from another class, called the superclass (or parent class). A class which inherits from a superclass is called a subclass, also called child class. Superclasses are sometimes called ancestors as well. There exists a hierarchy relationship between classes. It's similar to relationships or
categorizations that we know from real life. Think about vehicles, for example. Bikes, cars, buses and trucks are vehicles. pick-ups, vans, sports cars, convertibles and estate cars are all cars and by being cars they are vehicles as well. We could implement a vehicle class in Python, which might have methods like accelerate and brake. Cars, Buses and Trucks and Bikes can be implemented as subclasses which will inherit these methods from vehicle.

The syntax for a subclass definition looks like this:



Of course, usually we will have an indented block with the class attributes and methods instead of merely a pass statement. The name BaseClassName must be defined in a scope containing the derived class definition. With all this said, we can implement our Person and Employee class:
This is a good example:



The object 'y' inherited the methods from class 'Person' (which makes sense since an employee is also a person and has names), so it doesn't have to have the attibutes claimed in the class 'Person' claimed in its class 'Employee'.

The __init__ method of our Employee class explicitly invokes the __init__method of the Person class. We could have used super instead. super().__init__(first, last) is automatically replaced by a call to the superclasses method, in this case __init__:
Notes that the 'self' argument doesn't exist in 'super().__init__(first, last)'.



Please note that we used super() without arguments. This is only possible in Python3. We could have written "super(Employee, self).__init__(first, last, age)" which still works in Python3 and is compatible with Python2.

2. Overloading and overriding

Instead of using the methods "Name" and "GetEmployee" in our previous example, it might have been better to put this functionality into the "__str__" method. In doing so, we gain a lot, especially a cleaner design. We have a string casting for our classes and we can simply print out instances. Let's start with a __str__ method in Person:



Now, let's look at a code that was created to reach the same result but failed. Can you tell why it is not working?



One last note: The following doesn't work:



The Employee class inherits the 'self.firstname and the self.lastname' feature form the Person class by 'super().__init__(fisrt, last)'. So in the child class, you can selectively inherit some of the features in the parent class. Which is great. You don't want to inherit the ones you don't need.

However, the 'super().__init__(first, last)' call doesn't have 'self' as one of the arguments. Because you are calling the function __init__(self, first, last) in the Person class. and this function already has self in there and when you call it, you don't need to put 'self' in there. 'self' is kind of a default argument, you only have it when you define the function but you don't need it when you call this function.

The '__str__(self)' function doesn't have all the firstname, lastname, stuffnumber arguments. Why?
The '__str()' function is called when you have 'print()' called at the end of the script (outside of the class and function). If you print the entire object as 'print(freshMan)', then the argument 'freshMan' includes all the fisrtname, lastname, and stuffnumber in it, so including all these arguments in this call is redundant.

We have overridden the method __str__ from Person in Employee. By the way, we have overridden __init__ also. Method overriding is an object-oriented programming feature that allows a subclass to provide a different implementation of a method that is already defined by its superclass or by one of its superclasses. The implementation in the subclass overrides the implementation of the superclass by providing a method with the same name, same parameters or signature, and same return type as the method of the parent class.

3. The Car example:
Let's make the Car class first. We all know that the major attributes of a car can be: make, model, year, and odometer. We didn't put the odometer in the '__init__()' function since it is actually being changed along the way. But I still created a 'self.odometer_reading = 0' in the __init__() function to assign an initial value to the odometer. (it is a new car).



Then let's start making subclasses to this Car class:



Oops! Something is wrong. The '2016' in my 'ElectricCar("tesla", 'model s", 2016)' is an int variable. However, in the 'get_descriptive_name()' function, I didn't convert it into a string in order to be printed out later. So simply force it to a str variable there and you will fix it.



Thank Goodness! Objective-oriented programming is so fun!!!!



Tasks:

1. According to the given structure of the code and the given results, complete the following code and verify your results.



2.
According to the given structure of the code and the given results, complete the following code and verify your results.



3. Complete the following code:



4. Complete the following code: