teach_cs_toronto_edu_csc148h_notes_inheritance_inheritance_methods_html
teach_cs_toronto_edu_csc148h_notes_inheritance_inheritance_methods_html
2. Testing Your Code This design overlooks something important: employees of both types have many things in common. For instance, they all have
data like a name, address, and employee id. And even though their pay is computed differently, they all get paid. If we had two
2.1 Testing Your Work
classes for the two kinds of employees, all the code for these common elements would be duplicated. This is not only
2.2 Choosing Test Cases redundant but error prone: if you find a bug or make another kind of improvement in one class, you may forget to make the
2.3 Code Coverage same changes in the other class. Things get even worse if the company decides to add other kinds of employees, such as
3.3 The Class Design Recipe We define a base class that includes the functionality that are common to all employees.
3.4 More on Designing Classes We define a subclass for each specific type of employee. In each subclass, we declare that it is a kind of employee, which
will cause it to “inherit” those common elements from the base class without having to define them itself.
3.5 Inheritance: Introduction and
Methods Terminology note: if class B is a subclass of class A , we also say that A is a superclass of B .
3.6 Inheritance: Attributes and In our running example of a company with different kinds of employees, we could define a base class Employee and two
Initializers
subclasses as follows (for now, we will leave the class bodies empty):
3.7 Inheritance: Tracing Initialization
4.3 Exceptions
7.7 Recursion and the call stack Round the amount to the nearest cent.
"""
7.8 Branching recursion
pass # We still have to figure this out.
8. Trees and Binary Search Trees def pay(self, pay_date: date) -> None:
"""Pay this Employee on the given date and record the payment.
8.1 Introduction to Trees
(Assume this is called once per month.)
8.2 A Tree Implementation """
payment = self.get_monthly_payment()
8.3 Mutating Trees print(f'An employee was paid {payment} on {pay_date}.')
8.6 Mutating Binary Search Trees object needs to have. Subclasses will inherit this incomplete method, which they can redefine as appropriate. We’ll see how to
do that shortly.
8.7 Tree Traversals
8.8 Binary Search Trees and Notice that we did as much as we could in the base class, to avoid repeating code in the subclasses.
Running Time
class Employee:
"""An employee of a company.
It is possible for client code to ignore the warning and instantiate this class—Python does not prevent it. But look at what
happens when we try to call one of the unimplemented methods on the object:
>>> a = Employee()
>>> # This method is itself abstract:
>>> a.get_monthly_payment()
Traceback...
NotImplementedError
>>> # This method calls a helper method that is abstract:
>>> a.pay(date(2018, 9, 30))
Traceback...
NotImplementedError
>>> # Here we see what isinstance does with an object of a simple built-in type.
>>> isinstance(5, int)
True
>>> isinstance(5, str)
False
>>> # Now let's check how it works with objects of a type that we define.
>>> fred = SalariedEmployee()
>>> # fred's type is as we constructed it: SalariedEmployee.
>>> # More precisely, the object that fred refers to has type SalariedEmployee.
>>> type(fred)
<class 'employee.SalariedEmployee'>
>>> # In other words, the object is an instance of SalariedEmployee.
>>> isinstance(fred, SalariedEmployee)
True
>>> # Here's the important part: it is also an instance of Employee.
>>> isinstance(fred, Employee)
True
Because Python “knows” that fred is an instance of Employee , this object will have access to all methods of Employee ! We
say that fred inherits all of the Employee methods. So even if SalariedEmployee remains an empty class, its instances
can still call the methods get_monthly_payment and pay , because they are inherited.
class SalariedEmployee(Employee):
pass
the particular kind of employee. We accomplish this simply by defining the method again in the subclass.[2] We say that this
new method definition overrides the inherited definition:
class SalariedEmployee(Employee):
def get_monthly_payment(self) -> float:
# Assuming an annual salary of 60,000
return round(60000.0 / 12.0, 2)
class HourlyEmployee(Employee):
def get_monthly_payment(self) -> float:
# Assuming a 160-hour work month and a $20/hour wage.
return round(160.0 * 20.0, 2)
We now have a working version of all three classes, albeit a very limited one. Download and run the code that we've
written so far . You can experiment with it as you continue reading.
This is how it works: whenever code calls a.myMethod() , Python determines what type(a) is and looks in that class for
myMethod . If myMethod is found in this class, that method is called; otherwise, Python next searches for myMethod in the
superclass of the type of a , and then the superclass of the superclass, etc., until it either finds a definition of myMethod or it
has exhausted all possibilities, in which case it raises an AttributeError .
This method call is more interesting: fred.pay(date(2018, 9, 30)) . The value of type(fred) is SalariedEmployee , but
class SalariedEmployee does not contain a pay method. So Python next checks in the superclass Employee , which does
contain a pay method, so then that is called. Straightforward. But then inside Employee.pay , we have the call
self.get_monthly_payment() . Which get_monthly_payment is called? We’re already executing a method ( pay ) inside
the Employee class, but that doesn’t mean we call Employee.get_monthly_payment .[3] Remember the rule: type(self)
determines which class Python first looks in for the method. At this point, self is fred , whose type is SalariedEmployee ,
and that class contains a get_monthly_payment method. So in this case, when Employee.pay calls
self.get_monthly_payment() , it gets SalariedEmployee.get_monthly_payment .
[1] While we won’t follow it in this course, a common Python convention you’ll encounter is to put the word “abstract” in the
class name, e.g AbstractEmployee .
[2] For now, we will hard-code values in the method, but will generalize this later.
Previous Next
3.4 More on Designing Classes 3.6 Inheritance: Attributes and Initializers