Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
2 views

teach_cs_toronto_edu_csc148h_notes_inheritance_inheritance_attributes_html

CSC148 Notes

Uploaded by

racayow849
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

teach_cs_toronto_edu_csc148h_notes_inheritance_inheritance_attributes_html

CSC148 Notes

Uploaded by

racayow849
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 1

CSC148 Course Notes 3.

6 Inheritance: Attributes and Initializers


CSC148 Course Notes Let’s return to  the payroll code we wrote and generalize it from hard-coded values to instance attributes. This will allow
us to customize individual employees with their own annual salaries or hourly wages.
1. Recapping and Extending Some
Key Prerequisite Material

1.1 The Python Memory Model: Documenting attributes


Introduction
Just as the base class contains methods (even abstract ones!) that all subclasses need to have in common, the base class
1.2 The Python Memory Model:
also documents attributes that all subclasses need to have in common. Both are a fundamental part of the public interface of a
Functions and Parameters
class.
1.3 The Function Design Recipe

1.4 Preconditions We decided earlier that the application would need to record an id and a name for all employees. Here’s how we document
that in the base class:[1]
1.5 Python Type Annotations

2. Testing Your Code class Employee:


"""An employee of a company.
2.1 Testing Your Work
Attributes:
2.2 Choosing Test Cases id_: This employee's ID number.
name: This employee's name.
2.3 Code Coverage """
id_: int
2.4 Introduction to Property-Based
name: str
Testing

3. Object-Oriented Programming

3.1 Introduction to Object-Oriented Defining an initializer in the abstract superclass


Programming
Even though abstract classes should not be instantiated directly, we provide an initializer in the superclass to initialize the
3.2 Representation Invariants
common attributes.
3.3 The Class Design Recipe

3.4 More on Designing Classes class Employee:


def __init__(self, id_: int, name: str) -> None:
3.5 Inheritance: Introduction and """Initialize this employee.
Methods """
self.id_ = id_
3.6 Inheritance: Attributes and self.name = name
Initializers
def get_monthly_payment(self) -> float:
3.7 Inheritance: Tracing Initialization """Return the amount that this Employee should be paid in one month.

3.8 Inheritance: Thoughts on Design Round the amount to the nearest cent.
"""
3.9 The object Class and Python raise NotImplementedError
Special Methods
def pay(self, date: str) -> None:
"""Pay this Employee on the given date and record the payment.
4. Abstract Data Types
(Assume this is called once per month.)
4.1 Introduction to Abstract Data """
Types payment = self.get_monthly_payment()
print(f'An employee was paid {payment} on {date}.')
4.2 Stacks and Queues

4.3 Exceptions

4.4 Analysing Program Running


Time
Inheriting the initializer in a subclass
Because the initializer is a method, it is automatically inherited by all Employee subclasses just as, for instance, pay is.
5. Exceptions

5.1 Introduction to Exceptions >>> # Assuming SalariedEmployee does not override Employee.__init__,
>>> # that method is called when we construct a SalariedEmployee.
5.2 General Rules for try-except
>>> fred = SalariedEmployee(99, 'Fred Flintstone')
5.3 Why Not Just Return a Special >>> # We can see that Employee.__init__ was called,
>>> # and the two instance attributes have been initialized.
Value? >>> fred.name
'Fred Flintstone'
5.4 Additional Clauses
>>> fred.id_
99
6. Linked Lists

6.1 Introduction to Linked Lists Just as with all other methods, for each subclass, we must decide whether the inherited implementation is suitable for our
6.2 Traversing Linked Lists class, or whether we want to override it. In this case, the inherited initializer is not suitable, because each subclass requires
that additional instance attributes be initialized: For each SalariedEmployee we need to keep track of the employee’s salary,
6.3 Linked List Mutation
and for each HourlyEmployee we need to keep track of their number of work hours per week and their hourly wage.
6.4 Linked Lists and Running Time

Certainly we could override and replace the inherited initializer, and in its body copy the code from Employee.__init__ :
7. Recursion

7.1 Motivation: Adding Up Numbers class SalariedEmployee(Employee):


def __init__(self, id_: int, name: str, salary: float) -> None:
7.2 Nested Lists: A Recursive Data self.id_ = id_ # Copied from Employee.__init__
Structure self.name = name # Copied from Employee.__init__
self.salary = salary # Specific to SalariedEmployee
7.3 Understanding Recursive
Functions: Partial Tracing class HourlyEmployee(Employee):
def __init__(self, id_: int, name: str, hourly_wage: float,
7.4 Writing Recursive Functions: hours_per_month: float) -> None:
self.id_ = id_ # Copied from Employee.__init__
Using the Recursive Structure of the
self.name = name # Copied from Employee.__init__
Problem self.hourly_wage = hourly_wage # Specific to HourlyEmployee
self.hours_per_month = hours_per_month # Specific to HourlyEmployee
7.5 Writing Recursive Functions:
Using a Set of Test Scenarios
This is not a very satisfying solution because the first two lines of each initializer are duplicated—and for more complex
7.6 How Recursive Code Can Fail
abstract base classes, the problem would be even worse!
7.7 Recursion and the call stack

7.8 Branching recursion Since the inherited initializer does part of the work by initializing the attributes that all employees have in common, we can
instead use Employee.__init__ as a helper method. In other words, rather than override and replace this method, we will
8. Trees and Binary Search Trees override and extend it. As we saw briefly last week, we use the superclass name to access its method:[2]
8.1 Introduction to Trees

8.2 A Tree Implementation class SalariedEmployee(Employee):


def __init__(self, id_: int, name: str, salary: float) -> None:
8.3 Mutating Trees # Note that to call the superclass initializer, we need to use the
# full method name '__init__'. This is the only time you should write
8.4 Introduction to Binary Search # '__init__' explicitly.
Trees Employee.__init__(self, id_, name)
self.salary = salary
8.5 Binary Search Tree
Implementation and Search
In the subclasses, we also need to document each instance attribute and declare its type.[3] Here are the complete
8.6 Mutating Binary Search Trees
subclasses:
8.7 Tree Traversals

8.8 Binary Search Trees and class SalariedEmployee(Employee):


Running Time """
Attributes:
8.9 Expression Trees salary: This employee's annual salary

Representation Invariants:
9. Recursive Sorting Algorithms - self.salary >= 0
"""
9.1 Recursive Sorting Algorithms id_: int
name: str
9.2 Efficiency of Recursive Sorting salary: float
Algorithms
def __init__(self, id_: int, name: str, salary: float) -> None:
# Note that to call the superclass initializer, we need to use the
# full method name '__init__'. This is the only time you should write
# '__init__' explicitly.
Employee.__init__(self, id_, name)
self.salary = salary

def get_monthly_payment(self) -> float:


return round(self.salary / 12, 2)

class HourlyEmployee(Employee):
"""An employee whose pay is computed based on an hourly rate.

Attributes:
hourly_wage:
This employee's hourly rate of pay.
hours_per_month:
The number of hours this employee works each month.

Representation Invariants:
- self.hourly_wage >= 0
- self.hours_per_month >= 0
"""
id_: int
name: str
hourly_wage: float
hours_per_month: float

def __init__(self, id_: int, name: str, hourly_wage: float,


hours_per_month: float) -> None:
Employee.__init__(self, id_, name)
self.hourly_wage = hourly_wage
self.hours_per_month = hours_per_month

def get_monthly_payment(self) -> float:


return round(self.hours_per_month * self.hourly_wage, 2)

We can see that when we construct an instance of either subclass, both the common instance attributes ( name and id_ ) and
the subclass-specific attributes are initialized:

>>> fred = SalariedEmployee(99, 'Fred Flintstone', 60000.0)


>>> fred.name
'Fred Flintstone'
>>> fred.salary
60000
>>> barney = HourlyEmployee(23, 'Barney Rubble', 1.25, 50.0)
>>> barney.name
'Barney Rubble'
>>> barney.hourly_wage
1.25
>>> barney.hours_per_month
50.0

We have now completed the  second version of the code . Download it so that you can experiment with it as you
continue reading.

Subclasses inherit methods, not attributes


It may seem that our two subclasses have “inherited” the attributes documented in the Employee class.[4] But remember that
a type annotation does not create a variable. Consider this example:

>>> fred = SalariedEmployee(99, 'Fred Flintstone', 60000.0)


>>> fred.name
'Fred Flintstone'

The only reason that fred has a name attribute is because the SalariedEmployee initializer explicitly calls the Employee
initializer, which initializes this attribute. A superclass initializer is not called automatically when a subclass instance is created.
If we remove this call from our example, we see that the two attributes name and id_ are missing:

class SalariedEmployee(Employee):
def __init__(self, id_: int, name: str, salary: float) -> None:
# Superclass call commented out:
# Employee.__init__(self, id_, name)
self.salary = salary

>>> fred = SalariedEmployee('Fred Flintstone')


>>> fred.name
AttributeError

Initializers with different signatures


Notice that the signatures for Employee.__init__ and SalariedEmployee.__init__ are different.
SalariedEmployee.__init__ has an additional parameter for the salary. This makes sense. We should be able to configure
each salaried employee with their own salary, but it is irrelevant to other types of employee, who don’t have a salary.

Because abstract classes aren’t meant to be instantiated directly, their initializers are considered private, and so can be freely
overridden and have their signatures changed in each subclass. This offers flexibility in specifying how subclasses are created,
and in fact it is often the case that different subclasses of the same abstract class will have different initializer signatures.
However, subclass initializers should always call the initializer of their superclass!

It turns out that Python allows us to change the signature of any method we override, not just __init__ . However, as we’ll
discuss in the next section, in this course we’ll use inheritance to define interfaces that your subclasses should implement.
Because a function signature is a crucial part of its interface, you should not do this for uses of inheritance in this course.

[1] We put an underscore at the end of the attribute id_ in order to distinguish it from the built-in function id .

[2] Python has a much more powerful mechanism for accessing the superclass without naming it directly. It involves the built-
in super function, but this is beyond the scope of this course.

[3] In this course, we also include type annotations from the parent class. For a technical reason, the current version of
python_ta sometimes complains when these type annotations are missing.

[4] In many other languages, instance attributes are inherited.

Previous Next
 3.5 Inheritance: Introduction and Methods 3.7 Inheritance: Tracing Initialization

By Diane Horton and David Liu


© Copyright 2022.

You might also like