0% found this document useful (0 votes)
10 views1 page

teach_cs_toronto_edu_csc148h_notes_inheritance_inheritance_methods_html

CSC148 Notes

Uploaded by

racayow849
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views1 page

teach_cs_toronto_edu_csc148h_notes_inheritance_inheritance_methods_html

CSC148 Notes

Uploaded by

racayow849
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1

CSC148 Course Notes 3.

5 Inheritance: Introduction and Methods


CSC148 Course Notes In this and the next few sections, we’ll learn about a relationship called inheritance that can exist between two classes. We will
focus on one particular way of using inheritance in our code design, and through that, will learn how inheritance works in
1. Recapping and Extending Some Python.
Key Prerequisite Material

1.1 The Python Memory Model:


Introduction Consider a payroll system
1.2 The Python Memory Model:
Suppose we are designing an application to keep track of a company’s employees, including some who are paid based on an
Functions and Parameters
annual salary and others who are paid based on an hourly wage. We might choose to define a separate class for these two
1.3 The Function Design Recipe types of employee so that they could have different attributes. For instance, only a salaried employee has a salary to be
1.4 Preconditions stored, and only an hourly-paid employee has an hourly wage to be recorded. The classes could also have different methods
1.5 Python Type Annotations for the different ways that their pay is computed.

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

2.4 Introduction to Property-Based those working on commission.


Testing

3. Object-Oriented Programming Inheritance to the rescue!


3.1 Introduction to Object-Oriented
A better design would “factor out” the things that are common to all employees and write them once. This is what inheritance
Programming
allows us to do, and here is how:
3.2 Representation Invariants

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

3.8 Inheritance: Thoughts on Design class Employee:


pass
3.9 The object Class and Python
Special Methods # We use the "(Employee)" part to mark SalariedEmployee as a subclass of Employee.
class SalariedEmployee(Employee):
pass
4. Abstract Data Types
# We use the "(Employee)" part to mark HourlyEmployee as a subclass of Employee.
4.1 Introduction to Abstract Data class HourlyEmployee(Employee):
Types pass

4.2 Stacks and Queues

4.3 Exceptions

4.4 Analysing Program Running


Inheritance terminology
Time
It’s useful to know that there are three ways to talk about classes that are in an inheritance relationship:

5. Exceptions base class, superclass, and parent class are synonyms.


5.1 Introduction to Exceptions derived class, subclass, and child class are synonyms.
5.2 General Rules for try-except

5.3 Why Not Just Return a Special


Value? Defining methods in a base class
5.4 Additional Clauses Now let’s fill in these classes, starting with the methods we want. Once we have that, we can figure out the data that will be
needed to implement the methods. To keep our example simple, let’s say that we only need a method for paying an employee,
6. Linked Lists
and that it will just print out a statement saying when and how much the employee was paid.
6.1 Introduction to Linked Lists
Here’s the outline for the Employee class with a pay method.
6.2 Traversing Linked Lists

6.3 Linked List Mutation


class Employee:
6.4 Linked Lists and Running Time """An employee of a company.
"""

7. Recursion def pay(self, pay_date: date) -> None:


"""Pay this Employee on the given date and record the payment.
7.1 Motivation: Adding Up Numbers
(Assume this is called once per month.)
7.2 Nested Lists: A Recursive Data """
Structure pass

7.3 Understanding Recursive


Functions: Partial Tracing If we try to write the body of the pay method, we run into a problem: it must compute the appropriate pay amount for printing,
7.4 Writing Recursive Functions: and that must be done differently for each type of employee. Our solution is to pull that part out into a helper method:
Using the Recursive Structure of the
Problem class Employee:
"""An employee of a company.
7.5 Writing Recursive Functions: """
Using a Set of Test Scenarios
def get_monthly_payment(self) -> float:
7.6 How Recursive Code Can Fail """Return the amount that this Employee should be paid in one month.

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.4 Introduction to Binary Search


Trees Now method pay is complete, but we have the same problem with get_monthly_payment : it has to be different for each type
8.5 Binary Search Tree of employee. Clearly, the subclasses must define this method, each in their own way. But we are going to leave the incomplete
Implementation and Search get_monthly_payment method in the Employee class, because it defines part of the interface that every type of Employee

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

8.9 Expression Trees


Making the base class abstract
9. Recursive Sorting Algorithms
Because the Employee class has a method with no body, client code should not make instances of this incomplete class
9.1 Recursive Sorting Algorithms
directly. We do two things to make this clear:
9.2 Efficiency of Recursive Sorting
Algorithms Change the body of the incomplete method so that it simply raises a NotImplementedError . We call such a method an
abstract method, and we call a class which has at least one abstract method an abstract class.
Add a comment to the class docstring stating that the class is abstract, so that anyone writing client code is warned not to
instantiate it.[1]

Here is the complete definition of our class:

class Employee:
"""An employee of a company.

This is an abstract class. Only subclasses should be instantiated.


"""
def get_monthly_payment(self) -> float:
"""Return the amount that this Employee should be paid in one month.

Round the amount to the nearest cent.


"""
raise NotImplementedError

def pay(self, pay_date: date) -> None:


"""Pay this Employee on the given date and record the payment.

(Assume this is called once per month.)


"""
payment = self.get_monthly_payment()
print(f'An employee was paid {payment} on {pay_date}.')

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

Subclasses inherit from the base class


Now let’s fill in class SalariedEmployee , which is a subclass of Employee . Very importantly, all instances of
SalariedEmployee are also instances of Employee . We can verify this using the built-in function isinstance :

>>> # 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

>>> fred = SalariedEmployee()


>>> # fred inherits Employee.get_monthly_payment, and so can call it.
>>> # Of course, it raises an error when called, but it indeed is accessed.
>>> fred.get_monthly_payment()
Traceback...
NotImplementedError

Completing the subclass


Our SalariedEmployee and HourlyEmployee subclasses each inherit two methods: pay and get_monthly_payment . The
method pay is complete as it is, and is appropriate for all types of employees, so we needn’t do anything with it. However,
get_monthly_payment needs a new definition that does not raise an error and that defines the behaviour appropriately for

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)

>>> fred = SalariedEmployee()


>>> fred.get_monthly_payment()
5000.0
>>> jerry = HourlyEmployee()
>>> jerry.get_monthly_payment()
3200.0

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.

How Python resolves a method name


The interaction above includes the call fred.get_monthly_payment() . Since the name get_monthly_payment could refer
to several possible methods (one in class Employee , one in class SalariedEmployee , and one in class HourlyEmployee ),
we say that the method name must be “resolved”. To understand inheritance, we need to know how Python handles method
resolution in general.

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 .

In the case of the call fred.get_monthly_payment() , type(fred) is SalariedEmployee , and SalariedEmployee


contains a get_monthly_payment method. So that is the one called.

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.

[3] Good thing, because it raises an error!

Previous Next
 3.4 More on Designing Classes 3.6 Inheritance: Attributes and Initializers

By Diane Horton and David Liu


© Copyright 2022.

You might also like