Data_Structures_and_Algorithms_in_Python_----_(2_Object-Oriented_Programming)
Data_Structures_and_Algorithms_in_Python_----_(2_Object-Oriented_Programming)
2 Object-Oriented Programming
Contents
2.1 Goals, Principles, and Patterns . . . . . . . . . . . . . . . . 57
2.1.1 Object-Oriented Design Goals . . . . . . . . . . . . . . . 57
2.1.2 Object-Oriented Design Principles . . . . . . . . . . . . . 58
2.1.3 Design Patterns . . . . . . . . . . . . . . . . . . . . . . . 61
2.2 Software Development . . . . . . . . . . . . . . . . . . . . 62
2.2.1 Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
2.2.2 Pseudo-Code . . . . . . . . . . . . . . . . . . . . . . . . 64
2.2.3 Coding Style and Documentation . . . . . . . . . . . . . . 64
2.2.4 Testing and Debugging . . . . . . . . . . . . . . . . . . . 67
2.3 Class Definitions . . . . . . . . . . . . . . . . . . . . . . . . 69
2.3.1 Example: CreditCard Class . . . . . . . . . . . . . . . . . 69
2.3.2 Operator Overloading and Python’s Special Methods . . . 74
2.3.3 Example: Multidimensional Vector Class . . . . . . . . . . 77
2.3.4 Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
2.3.5 Example: Range Class . . . . . . . . . . . . . . . . . . . . 80
2.4 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
2.4.1 Extending the CreditCard Class . . . . . . . . . . . . . . . 83
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.1. Goals, Principles, and Patterns 57
Robustness
Every good programmer wants to develop software that is correct, which means that
a program produces the right output for all the anticipated inputs in the program’s
application. In addition, we want software to be robust, that is, capable of handling
unexpected inputs that are not explicitly defined for its application. For example,
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
58 Chapter 2. Object-Oriented Programming
Adaptability
Modern software applications, such as Web browsers and Internet search engines,
typically involve large programs that are used for many years. Software, there-
fore, needs to be able to evolve over time in response to changing conditions in its
environment. Thus, another important goal of quality software is that it achieves
adaptability (also called evolvability). Related to this concept is portability, which
is the ability of software to run with minimal change on different hardware and
operating system platforms. An advantage of writing software in Python is the
portability provided by the language itself.
Reusability
Going hand in hand with adaptability is the desire that software be reusable, that
is, the same code should be usable as a component of different systems in various
applications. Developing quality software can be an expensive enterprise, and its
cost can be offset somewhat if the software is designed in a way that makes it easily
reusable in future applications. Such reuse should be done with care, however, for
one of the major sources of software errors in the Therac-25 came from inappropri-
ate reuse of Therac-20 software (which was not object-oriented and not designed
for the hardware platform used with the Therac-25).
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.1. Goals, Principles, and Patterns 59
Modularity
Modern software systems typically consist of several different components that
must interact correctly in order for the entire system to work properly. Keeping
these interactions straight requires that these different components be well orga-
nized. Modularity refers to an organizing principle in which different components
of a software system are divided into separate functional units.
As a real-world analogy, a house or apartment can be viewed as consisting of
several interacting units: electrical, heating and cooling, plumbing, and structural.
Rather than viewing these systems as one giant jumble of wires, vents, pipes, and
boards, the organized architect designing a house or apartment will view them as
separate modules that interact in well-defined ways. In so doing, he or she is using
modularity to bring a clarity of thought that provides a natural way of organizing
functions into distinct manageable units.
In like manner, using modularity in a software system can also provide a pow-
erful organizing framework that brings clarity to an implementation. In Python,
we have already seen that a module is a collection of closely related functions and
classes that are defined together in a single file of source code. Python’s standard
libraries include, for example, the math module, which provides definitions for key
mathematical constants and functions, and the os module, which provides support
for interacting with the operating system.
The use of modularity helps support the goals listed in Section 2.1.1. Robust-
ness is greatly increased because it is easier to test and debug separate components
before they are integrated into a larger software system. Furthermore, bugs that per-
sist in a complete system might be traced to a particular component, which can be
fixed in relative isolation. The structure imposed by modularity also helps enable
software reusability. If software modules are written in a general way, the modules
can be reused when related need arises in other contexts. This is particularly rel-
evant in a study of data structures, which can typically be designed with sufficient
abstraction and generality to be reused in many applications.
Abstraction
Copyright © 2013. Wiley. All rights reserved.
The notion of abstraction is to distill a complicated system down to its most funda-
mental parts. Typically, describing the parts of a system involves naming them and
explaining their functionality. Applying the abstraction paradigm to the design of
data structures gives rise to abstract data types (ADTs). An ADT is a mathematical
model of a data structure that specifies the type of data stored, the operations sup-
ported on them, and the types of parameters of the operations. An ADT specifies
what each operation does, but not how it does it. We will typically refer to the
collective set of behaviors supported by an ADT as its public interface.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
60 Chapter 2. Object-Oriented Programming
As a programming language, Python provides a great deal of latitude in regard
to the specification of an interface. Python has a tradition of treating abstractions
implicitly using a mechanism known as duck typing. As an interpreted and dy-
namically typed language, there is no “compile time” checking of data types in
Python, and no formal requirement for declarations of abstract base classes. In-
stead programmers assume that an object supports a set of known behaviors, with
the interpreter raising a run-time error if those assumptions fail. The description
of this as “duck typing” comes from an adage attributed to poet James Whitcomb
Riley, stating that “when I see a bird that walks like a duck and swims like a duck
and quacks like a duck, I call that bird a duck.”
More formally, Python supports abstract data types using a mechanism known
as an abstract base class (ABC). An abstract base class cannot be instantiated
(i.e., you cannot directly create an instance of that class), but it defines one or more
common methods that all implementations of the abstraction must have. An ABC
is realized by one or more concrete classes that inherit from the abstract base class
while providing implementations for those method declared by the ABC. Python’s
abc module provides formal support for ABCs, although we omit such declarations
for simplicity. We will make use of several existing abstract base classes coming
from Python’s collections module, which includes definitions for several common
data structure ADTs, and concrete implementations of some of those abstractions.
Encapsulation
Another important principle of object-oriented design is encapsulation. Different
components of a software system should not reveal the internal details of their
respective implementations. One of the main advantages of encapsulation is that it
gives one programmer freedom to implement the details of a component, without
concern that other programmers will be writing code that intricately depends on
those internal decisions. The only constraint on the programmer of a component
is to maintain the public interface for the component, as other programmers will
be writing code that depends on that interface. Encapsulation yields robustness
and adaptability, for it allows the implementation details of parts of a program to
change without adversely affecting other parts, thereby making it easier to fix bugs
or add new functionality with relatively local changes to a component.
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.1. Goals, Principles, and Patterns 61
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
62 Chapter 2. Object-Oriented Programming
2.2.1 Design
For object-oriented programming, the design step is perhaps the most important
phase in the process of developing software. For it is in the design step that we
decide how to divide the workings of our program into classes, we decide how
these classes will interact, what data each will store, and what actions each will
perform. Indeed, one of the main challenges that beginning programmers face is
deciding what classes to define to do the work of their program. While general
prescriptions are hard to come by, there are some rules of thumb that we can apply
when determining how to design our classes:
• Responsibilities: Divide the work into different actors, each with a different
responsibility. Try to describe responsibilities using action verbs. These
actors will form the classes for the program.
• Independence: Define the work for each class to be as independent from
other classes as possible. Subdivide responsibilities between classes so that
each class has autonomy over some aspect of the program. Give data (as in-
stance variables) to the class that has jurisdiction over the actions that require
access to this data.
• Behaviors: Define the behaviors for each class carefully and precisely, so
that the consequences of each action performed by a class will be well un-
derstood by other classes that interact with it. These behaviors will define
Copyright © 2013. Wiley. All rights reserved.
the methods that this class performs, and the set of behaviors for a class are
the interface to the class, as these form the means for other pieces of code to
interact with objects from the class.
Defining the classes, together with their instance variables and methods, are key
to the design of an object-oriented program. A good programmer will naturally
develop greater skill in performing these tasks over time, as experience teaches
him or her to notice patterns in the requirements of a program that match patterns
that he or she has seen before.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.2. Software Development 63
A common tool for developing an initial high-level design for a project is the
use of CRC cards. Class-Responsibility-Collaborator (CRC) cards are simple in-
dex cards that subdivide the work required of a program. The main idea behind this
tool is to have each card represent a component, which will ultimately become a
class in the program. We write the name of each component on the top of an index
card. On the left-hand side of the card, we begin writing the responsibilities for
this component. On the right-hand side, we list the collaborators for this compo-
nent, that is, the other components that this component will have to interact with to
perform its duties.
The design process iterates through an action/actor cycle, where we first iden-
tify an action (that is, a responsibility), and we then determine an actor (that is, a
component) that is best suited to perform that action. The design is complete when
we have assigned all actions to actors. In using index cards for this process (rather
than larger pieces of paper), we are relying on the fact that each component should
have a small set of responsibilities and collaborators. Enforcing this rule helps keep
the individual classes manageable.
As the design takes form, a standard approach to explain and document the
design is the use of UML (Unified Modeling Language) diagrams to express the
organization of a program. UML diagrams are a standard visual notation to express
object-oriented software designs. Several computer-aided tools are available to
build UML diagrams. One type of UML figure is known as a class diagram. An
example of such a diagram is given in Figure 2.3, for a class that represents a
consumer credit card. The diagram has three portions, with the first designating
the name of the class, the second designating the recommended instance variables,
and the third designating the recommended methods of the class. In Section 2.2.3,
we discuss our naming conventions, and in Section 2.3.1, we provide a complete
implementation of a Python CreditCard class based on this design.
Class: CreditCard
Fields: customer balance
bank limit
Copyright © 2013. Wiley. All rights reserved.
account
Behaviors: get customer( ) get balance( )
get bank( ) get limit( )
get account( ) charge(price)
make payment(amount)
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
64 Chapter 2. Object-Oriented Programming
2.2.2 Pseudo-Code
As an intermediate step before the implementation of a design, programmers are
often asked to describe algorithms in a way that is intended for human eyes only.
Such descriptions are called pseudo-code. Pseudo-code is not a computer program,
but is more structured than usual prose. It is a mixture of natural language and
high-level programming constructs that describe the main ideas behind a generic
implementation of a data structure or algorithm. Because pseudo-code is designed
for a human reader, not a computer, we can communicate high-level ideas, without
being burdened with low-level implementation details. At the same time, we should
not gloss over important steps. Like many forms of human communication, finding
the right balance is an important skill that is refined through practice.
In this book, we rely on a pseudo-code style that we hope will be evident to
Python programmers, yet with a mix of mathematical notations and English prose.
For example, we might use the phrase “indicate an error” rather than a formal raise
statement. Following conventions of Python, we rely on indentation to indicate
the extent of control structures and on an indexing notation in which entries of a
sequence A with length n are indexed from A[0] to A[n − 1]. However, we choose
to enclose comments within curly braces { like these } in our pseudo-code, rather
than using Python’s # character.
http://www.python.org/dev/peps/pep-0008/
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.2. Software Development 65
• Use meaningful names for identifiers. Try to choose names that can be read
aloud, and choose names that reflect the action, responsibility, or data each
identifier is naming.
◦ Classes (other than Python’s built-in classes) should have a name that
serves as a singular noun, and should be capitalized (e.g., Date rather
than date or Dates). When multiple words are concatenated to form a
class name, they should follow the so-called “CamelCase” convention
in which the first letter of each word is capitalized (e.g., CreditCard).
if n % 2 == 1: # n is odd
Multiline block comments are good for explaining more complex code sec-
tions. In Python, these are technically multiline string literals, typically de-
limited with triple quotes (”””), which have no effect when executed. In the
next section, we discuss the use of block comments for documentation.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
66 Chapter 2. Object-Oriented Programming
Documentation
pydoc is distributed with Python and can be used to generate formal documentation
as text or as a Web page. Guidelines for authoring useful docstrings are available
at:
http://www.python.org/dev/peps/pep-0257/
In this book, we will try to present docstrings when space allows. Omitted
docstrings can be found in the online version of our source code.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.2. Software Development 67
Testing
A careful testing plan is an essential part of writing a program. While verifying the
correctness of a program over all possible inputs is usually infeasible, we should
aim at executing the program on a representative subset of inputs. At the very
minimum, we should make sure that every method of a class is tested at least once
(method coverage). Even better, each code statement in the program should be
executed at least once (statement coverage).
Programs often tend to fail on special cases of the input. Such cases need to be
carefully identified and tested. For example, when testing a method that sorts (that
is, puts in order) a sequence of integers, we should consider the following inputs:
• The sequence has zero length (no elements).
• The sequence has one element.
• All the elements of the sequence are the same.
• The sequence is already sorted.
• The sequence is reverse sorted.
In addition to special inputs to the program, we should also consider special
conditions for the structures used by the program. For example, if we use a Python
list to store data, we should make sure that boundary cases, such as inserting or
removing at the beginning or end of the list, are properly handled.
While it is essential to use handcrafted test suites, it is also advantageous to
run the program on a large collection of randomly generated inputs. The random
module in Python provides several means for generating random numbers, or for
randomizing the order of collections.
The dependencies among the classes and functions of a program induce a hi-
erarchy. Namely, a component A is above a component B in the hierarchy if A
depends upon B, such as when function A calls function B, or function A relies on
Copyright © 2013. Wiley. All rights reserved.
a parameter that is an instance of class B. There are two main testing strategies,
top-down and bottom-up, which differ in the order in which components are tested.
Top-down testing proceeds from the top to the bottom of the program hierarchy.
It is typically used in conjunction with stubbing, a boot-strapping technique that
replaces a lower-level component with a stub, a replacement for the component
that simulates the functionality of the original. For example, if function A calls
function B to get the first line of a file, when testing A we can replace B with a stub
that returns a fixed string.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
68 Chapter 2. Object-Oriented Programming
Bottom-up testing proceeds from lower-level components to higher-level com-
ponents. For example, bottom-level functions, which do not invoke other functions,
are tested first, followed by functions that call only bottom-level functions, and so
on. Similarly a class that does not depend upon any other classes can be tested
before another class that depends on the former. This form of testing is usually
described as unit testing, as the functionality of a specific component is tested in
isolation of the larger software project. If used properly, this strategy better isolates
the cause of errors to the component being tested, as lower-level components upon
which it relies should have already been thoroughly tested.
Python provides several forms of support for automated testing. When func-
tions or classes are defined in a module, testing for that module can be embedded
in the same file. The mechanism for doing so was described in Section 1.11. Code
that is shielded in a conditional construct of the form
if name == __main__ :
# perform tests...
will be executed when Python is invoked directly on that module, but not when the
module is imported for use in a larger software project. It is common to put tests
in such a construct to test the functionality of the functions and classes specifically
defined in that module.
More robust support for automation of unit testing is provided by Python’s
unittest module. This framework allows the grouping of individual test cases into
larger test suites, and provides support for executing those suites, and reporting or
analyzing the results of those tests. As software is maintained, the act of regression
testing is used, whereby all previous tests are re-executed to ensure that changes to
the software do not introduce new bugs in previously tested components.
Debugging
The simplest debugging technique consists of using print statements to track the
values of variables during the execution of the program. A problem with this ap-
proach is that eventually the print statements need to be removed or commented
out, so they are not executed when the software is finally released.
A better approach is to run the program within a debugger, which is a special-
Copyright © 2013. Wiley. All rights reserved.
ized environment for controlling and monitoring the execution of a program. The
basic functionality provided by a debugger is the insertion of breakpoints within
the code. When the program is executed within the debugger, it stops at each
breakpoint. While the program is stopped, the current value of variables can be
inspected.
The standard Python distribution includes a module named pdb, which provides
debugging support directly within the interpreter. Most IDEs for Python, such as
IDLE, provide debugging environments with graphical user interfaces.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.3. Class Definitions 69
maintain its own balance, its own credit limit, and so on. Therefore, each instance
stores its own instance variables to reflect its current state.
Syntactically, self identifies the instance upon which a method is invoked. For
example, assume that a user of our class has a variable, my card, that identifies
an instance of the CreditCard class. When the user calls my card.get balance( ),
identifier self, within the definition of the get balance method, refers to the card
known as my card by the caller. The expression, self. balance refers to an instance
variable, named balance, stored as part of that particular credit card’s state.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
70 Chapter 2. Object-Oriented Programming
1 class CreditCard:
2 ”””A consumer credit card.”””
3
4 def init (self, customer, bank, acnt, limit):
5 ”””Create a new credit card instance.
6
7 The initial balance is zero.
8
9 customer the name of the customer (e.g., John Bowman )
10 bank the name of the bank (e.g., California Savings )
11 acnt the acount identifier (e.g., 5391 0375 9387 5309 )
12 limit credit limit (measured in dollars)
13 ”””
14 self. customer = customer
15 self. bank = bank
16 self. account = acnt
17 self. limit = limit
18 self. balance = 0
19
20 def get customer(self):
21 ”””Return name of the customer.”””
22 return self. customer
23
24 def get bank(self):
25 ”””Return the bank s name.”””
26 return self. bank
27
28 def get account(self):
29 ”””Return the card identifying number (typically stored as a string).”””
30 return self. account
31
32 def get limit(self):
33 ”””Return current credit limit.”””
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.3. Class Definitions 71
39 def charge(self, price):
40 ”””Charge given price to the card, assuming sufficient credit limit.
41
42 Return True if charge was processed; False if charge was denied.
43 ”””
44 if price + self. balance > self. limit: # if charge would exceed limit,
45 return False # cannot accept charge
46 else:
47 self. balance += price
48 return True
49
50 def make payment(self, amount):
51 ”””Process customer payment that reduces balance.”””
52 self. balance −= amount
Code Fragment 2.2: The conclusion of the CreditCard class definition (continued
from Code Fragment 2.1). These methods are indented within the class definition.
The Constructor
A user can create an instance of the CreditCard class using a syntax as:
cc = CreditCard( John Doe, 1st Bank , 5391 0375 9387 5309 , 1000)
Internally, this results in a call to the specially named init method that serves
as the constructor of the class. Its primary responsibility is to establish the state of
Copyright © 2013. Wiley. All rights reserved.
a newly created credit card object with appropriate instance variables. In the case
of the CreditCard class, each object maintains five instance variables, which we
name: customer, bank, account, limit, and balance. The initial values for the
first four of those five are provided as explicit parameters that are sent by the user
when instantiating the credit card, and assigned within the body of the construc-
tor. For example, the command, self. customer = customer, assigns the instance
variable self. customer to the parameter customer; note that because customer is
unqualified on the right-hand side, it refers to the parameter in the local namespace.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
72 Chapter 2. Object-Oriented Programming
Encapsulation
By the conventions described in Section 2.2.3, a single leading underscore in the
name of a data member, such as balance, implies that it is intended as nonpublic.
Users of a class should not directly access such members.
As a general rule, we will treat all data members as nonpublic. This allows
us to better enforce a consistent state for all instances. We can provide accessors,
such as get balance, to provide a user of our class read-only access to a trait. If
we wish to allow the user to change the state, we can provide appropriate update
methods. In the context of data structures, encapsulating the internal representation
allows us greater flexibility to redesign the way a class works, perhaps to improve
the efficiency of the structure.
Additional Methods
The most interesting behaviors in our class are charge and make payment. The
charge function typically adds the given price to the credit card balance, to reflect
a purchase of said price by the customer. However, before accepting the charge,
our implementation verifies that the new purchase would not cause the balance to
exceed the credit limit. The make payment charge reflects the customer sending
payment to the bank for the given amount, thereby reducing the balance on the
card. We note that in the command, self. balance −= amount, the expression
self. balance is qualified with the self identifier because it represents an instance
variable of the card, while the unqualified amount represents the local parameter.
Error Checking
Our implementation of the CreditCard class is not particularly robust. First, we
note that we did not explicitly check the types of the parameters to charge and
make payment, nor any of the parameters to the constructor. If a user were to make
a call such as visa.charge( candy ), our code would presumably crash when at-
tempting to add that parameter to the current balance. If this class were to be widely
used in a library, we might use more rigorous techniques to raise a TypeError when
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.3. Class Definitions 73
Testing the Class
In Code Fragment 2.3, we demonstrate some basic usage of the CreditCard class,
inserting three cards into a list named wallet. We use loops to make some charges
and payments, and use various accessors to print results to the console.
These tests are enclosed within a conditional, if name == __main__ :,
so that they can be embedded in the source code with the class definition. Using
the terminology of Section 2.2.4, these tests provide method coverage, as each of
the methods is called at least once, but it does not provide statement coverage, as
there is never a case in which a charge is rejected due to the credit limit. This
is not a particular advanced from of testing as the output of the given tests must
be manually audited in order to determine whether the class behaved as expected.
Python has tools for more formal testing (see discussion of the unittest module
in Section 2.2.4), so that resulting values can be automatically compared to the
predicted outcomes, with output generated only when an error is detected.
53 if name == __main__ :
54 wallet = [ ]
55 wallet.append(CreditCard( John Bowman , California Savings ,
56 5391 0375 9387 5309 , 2500) )
57 wallet.append(CreditCard( John Bowman , California Federal ,
58 3485 0399 3395 1954 , 3500) )
59 wallet.append(CreditCard( John Bowman , California Finance ,
60 5391 0375 9387 5309 , 5000) )
61
62 for val in range(1, 17):
63 wallet[0].charge(val)
64 wallet[1].charge(2 val)
65 wallet[2].charge(3 val)
66
67 for c in range(3):
68 print( Customer = , wallet[c].get customer( ))
69 print( Bank = , wallet[c].get bank( ))
70 print( Account = , wallet[c].get account( ))
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
74 Chapter 2. Object-Oriented Programming
Non-Operator Overloads
In addition to traditional operator overloading, Python relies on specially named
methods to control the behavior of various other functionality, when applied to
user-defined classes. For example, the syntax, str(foo), is formally a call to the
constructor for the string class. Of course, if the parameter is an instance of a user-
Copyright © 2013. Wiley. All rights reserved.
defined class, the original authors of the string class could not have known how
that instance should be portrayed. So the string constructor calls a specially named
method, foo. str ( ), that must return an appropriate string representation.
Similar special methods are used to determine how to construct an int, float, or
bool based on a parameter from a user-defined class. The conversion to a Boolean
value is particularly important, because the syntax, if foo:, can be used even when
foo is not formally a Boolean value (see Section 1.4.1). For a user-defined class,
that condition is evaluated by the special method foo. bool ( ).
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.3. Class Definitions 75
bool(a) a. bool ( )
float(a) a. float ( )
int(a) a. int ( )
repr(a) a. repr ( )
reversed(a) a. reversed ( )
str(a) a. str ( )
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
76 Chapter 2. Object-Oriented Programming
Several other top-level functions rely on calling specially named methods. For
example, the standard way to determine the size of a container type is by calling
the top-level len function. Note well that the calling syntax, len(foo), is not the
traditional method-calling syntax with the dot operator. However, in the case of a
user-defined class, the top-level len function relies on a call to a specially named
len method of that class. That is, the call len(foo) is evaluated through a
method call, foo. len ( ). When developing data structures, we will routinely
define the len method to return a measure of the size of the structure.
Implied Methods
As a general rule, if a particular special method is not implemented in a user-defined
class, the standard syntax that relies upon that method will raise an exception. For
example, evaluating the expression, a + b, for instances of a user-defined class
without add or radd will raise an error.
However, there are some operators that have default definitions provided by
Python, in the absence of special methods, and there are some operators whose
definitions are derived from others. For example, the bool method, which
supports the syntax if foo:, has default semantics so that every object other than
None is evaluated as True. However, for container types, the len method is
typically defined to return the size of the container. If such a method exists, then
the evaluation of bool(foo) is interpreted by default to be True for instances with
nonzero length, and False for instances with zero length, allowing a syntax such as
if waitlist: to be used to test whether there are one or more entries in the waitlist.
In Section 2.3.4, we will discuss Python’s mechanism for providing iterators
for collections via the special method, iter . With that said, if a container class
provides implementations for both len and getitem , a default iteration is
provided automatically (using means we describe in Section 2.3.4). Furthermore,
once an iterator is defined, default functionality of contains is provided.
In Section 1.3 we drew attention to the distinction between expression a is b
and expression a == b, with the former evaluating whether identifiers a and b are
aliases for the same object, and the latter testing a notion of whether the two iden-
tifiers reference equivalent values. The notion of “equivalence” depends upon the
context of the class, and semantics is defined with the eq method. However, if
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.3. Class Definitions 77
automatically supports the syntax u = v + [5, 3, 10, −2, 1], resulting in a new
vector that is the element-by-element “sum” of the first vector and the list in-
stance. This is a result of Python’s polymorphism. Literally, “polymorphism”
means “many forms.” Although it is tempting to think of the other parameter of
our add method as another Vector instance, we never declared it as such.
Within the body, the only behaviors we rely on for parameter other is that it sup-
ports len(other) and access to other[j]. Therefore, our code executes when the
right-hand operand is a list of numbers (with matching length).
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
78 Chapter 2. Object-Oriented Programming
1 class Vector:
2 ”””Represent a vector in a multidimensional space.”””
3
4 def init (self, d):
5 ”””Create d-dimensional vector of zeros.”””
6 self. coords = [0] d
7
8 def len (self):
9 ”””Return the dimension of the vector.”””
10 return len(self. coords)
11
12 def getitem (self, j):
13 ”””Return jth coordinate of vector.”””
14 return self. coords[j]
15
16 def setitem (self, j, val):
17 ”””Set jth coordinate of vector to given value.”””
18 self. coords[j] = val
19
20 def add (self, other):
21 ”””Return sum of two vectors.”””
22 if len(self) != len(other): # relies on len method
23 raise ValueError( dimensions must agree )
24 result = Vector(len(self)) # start with vector of zeros
25 for j in range(len(self)):
26 result[j] = self[j] + other[j]
27 return result
28
29 def eq (self, other):
30 ”””Return True if vector has same coordinates as other.”””
31 return self. coords == other. coords
32
33 def ne (self, other):
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.3. Class Definitions 79
2.3.4 Iterators
1 class SequenceIterator:
2 ”””An iterator for any of Python s sequence types.”””
3
4 def init (self, sequence):
5 ”””Create an iterator for the given sequence.”””
6 self. seq = sequence # keep a reference to the underlying data
7 self. k = −1 # will increment to 0 on first call to next
8
9 def next (self):
10 ”””Return the next element, or else raise StopIteration error.”””
11 self. k += 1 # advance to next index
12 if self. k < len(self. seq):
13 return(self. seq[self. k]) # return the data element
Copyright © 2013. Wiley. All rights reserved.
14 else:
15 raise StopIteration( ) # there are no more elements
16
17 def iter (self):
18 ”””By convention, an iterator must return itself as an iterator.”””
19 return self
Code Fragment 2.5: An iterator class for any sequence type.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
80 Chapter 2. Object-Oriented Programming
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.3. Class Definitions 81
1 class Range:
2 ”””A class that mimic s the built-in range class.”””
3
4 def init (self, start, stop=None, step=1):
5 ”””Initialize a Range instance.
6
7 Semantics is similar to built-in range class.
8 ”””
9 if step == 0:
10 raise ValueError( step cannot be 0 )
11
12 if stop is None: # special case of range(n)
13 start, stop = 0, start # should be treated as if range(0,n)
14
15 # calculate the effective length once
16 self. length = max(0, (stop − start + step − 1) // step)
17
18 # need knowledge of start and step (but not stop) to support getitem
19 self. start = start
20 self. step = step
21
22 def len (self):
23 ”””Return number of entries in the range.”””
24 return self. length
25
26 def getitem (self, k):
27 ”””Return entry at index k (using standard interpretation if negative).”””
28 if k < 0:
29 k += len(self) # attempt to convert negative index
30
31 if not 0 <= k < self. length:
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
82 Chapter 2. Object-Oriented Programming
2.4 Inheritance
Building
There are two ways in which a subclass can differentiate itself from its su-
perclass. A subclass may specialize an existing behavior by providing a new im-
plementation that overrides an existing method. A subclass may also extend its
superclass by providing brand new methods.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.4. Inheritance 83
Python’s Exception Hierarchy
Another example of a rich inheritance hierarchy is the organization of various ex-
ception types in Python. We introduced many of those classes in Section 1.7, but
did not discuss their relationship with each other. Figure 2.5 illustrates a (small)
portion of that hierarchy. The BaseException class is the root of the entire hierar-
chy, while the more specific Exception class includes most of the error types that
we have discussed. Programmers are welcome to define their own special exception
classes to denote errors that may occur in the context of their application. Those
user-defined exception types should be declared as subclasses of Exception.
BaseException
ing a monthly interest charge on the outstanding balance, based upon an Annual
Percentage Rate (APR) specified as a constructor parameter.
In accomplishing this goal, we demonstrate the techniques of specialization
and extension. To charge a fee for an invalid charge attempt, we override the
existing charge method, thereby specializing it to provide the new functionality
(although the new version takes advantage of a call to the overridden version). To
provide support for charging interest, we extend the class with a new method named
process month.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
84 Chapter 2. Object-Oriented Programming
Class: CreditCard
Fields: customer balance
bank limit
account
Behaviors: get customer( ) get balance( )
get bank( ) get limit( )
get account( ) charge(price)
make payment(amount)
Class: PredatoryCreditCard
Fields: apr
Behaviors: process month( ) charge(price)
Figure 2.6 provides an overview of our use of inheritance in designing the new
PredatoryCreditCard class, and Code Fragment 2.7 gives a complete Python im-
plementation of that class.
To indicate that the new class inherits from the existing CreditCard class, our
definition begins with the syntax, class PredatoryCreditCard(CreditCard). The
body of the new class provides three member functions: init , charge, and
process month. The init constructor serves a very similar role to the original
CreditCard constructor, except that for our new class, there is an extra parameter
to specify the annual percentage rate. The body of our new constructor relies upon
making a call to the inherited constructor to perform most of the initialization (in
fact, everything other than the recording of the percentage rate). The mechanism
for calling the inherited constructor relies on the syntax, super( ). Specifically, at
line 15 the command
super( ). init (customer, bank, acnt, limit)
Copyright © 2013. Wiley. All rights reserved.
calls the init method that was inherited from the CreditCard superclass. Note
well that this method only accepts four parameters. We record the APR value in a
new field named apr.
In similar fashion, our PredatoryCreditCard class provides a new implemen-
tation of the charge method that overrides the inherited method. Yet, our imple-
mentation of the new method relies on a call to the inherited method, with syntax
super( ).charge(price) at line 24. The return value of that call designates whether
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.4. Inheritance 85
1 class PredatoryCreditCard(CreditCard):
2 ”””An extension to CreditCard that compounds interest and fees.”””
3
4 def init (self, customer, bank, acnt, limit, apr):
5 ”””Create a new predatory credit card instance.
6
7 The initial balance is zero.
8
9 customer the name of the customer (e.g., John Bowman )
10 bank the name of the bank (e.g., California Savings )
11 acnt the acount identifier (e.g., 5391 0375 9387 5309 )
12 limit credit limit (measured in dollars)
13 apr annual percentage rate (e.g., 0.0825 for 8.25% APR)
14 ”””
15 super( ). init (customer, bank, acnt, limit) # call super constructor
16 self. apr = apr
17
18 def charge(self, price):
19 ”””Charge given price to the card, assuming sufficient credit limit.
20
21 Return True if charge was processed.
22 Return False and assess 5 fee if charge is denied.
23 ”””
24 success = super( ).charge(price) # call inherited method
25 if not success:
26 self. balance += 5 # assess penalty
27 return success # caller expects return value
28
29 def process month(self):
30 ”””Assess monthly interest on outstanding balance.”””
31 if self. balance > 0:
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
86 Chapter 2. Object-Oriented Programming
the charge was successful. We examine that return value to decide whether to as-
sess a fee, and in turn we return that value to the caller of method, so that the new
version of charge has a similar outward interface as the original.
The process month method is a new behavior, so there is no inherited version
upon which to rely. In our model, this method should be invoked by the bank,
once each month, to add new interest charges to the customer’s balance. The most
challenging aspect in implementing this method is making sure we have working
knowledge of how an annual percentage rate translates to a monthly rate. We do
not simply divide the annual rate by twelve to get a monthly rate (that would be too
predatory, as it would result in a higher APR than advertised). The correct com-
putation is to take the twelfth-root of 1 + self. apr, and use that as a multiplica-
tive
√ factor. For example, if the APR is 0.0825 (representing 8.25%), we compute
12
1.0825 ≈ 1.006628, and therefore charge 0.6628% interest per month. In this
way, each $100 of debt will amass $8.25 of compounded interest in a year.
Protected Members
Our PredatoryCreditCard subclass directly accesses the data member self. balance,
which was established by the parent CreditCard class. The underscored name, by
convention, suggests that this is a nonpublic member, so we might ask if it is okay
that we access it in this fashion. While general users of the class should not be
doing so, our subclass has a somewhat privileged relationship with the superclass.
Several object-oriented languages (e.g., Java, C++) draw a distinction for nonpub-
lic members, allowing declarations of protected or private access modes. Members
that are declared as protected are accessible to subclasses, but not to the general
public, while members that are declared as private are not accessible to either. In
this respect, we are using balance as if it were protected (but not private).
Python does not support formal access control, but names beginning with a sin-
gle underscore are conventionally akin to protected, while names beginning with a
double underscore (other than special methods) are akin to private. In choosing to
use protected data, we have created a dependency in that our PredatoryCreditCard
class might be compromised if the author of the CreditCard class were to change
the internal design. Note that we could have relied upon the public get balance( )
method to retrieve the current balance within the process month method. But the
Copyright © 2013. Wiley. All rights reserved.
current design of the CreditCard class does not afford an effective way for a sub-
class to change the balance, other than by direct manipulation of the data member.
It may be tempting to use charge to add fees or interest to the balance. However,
that method does not allow the balance to go above the customer’s credit limit,
even though a bank would presumably let interest compound beyond the credit
limit, if warranted. If we were to redesign the original CreditCard class, we might
add a nonpublic method, set balance, that could be used by subclasses to affect a
change without directly accessing the data member balance.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.4. Inheritance 87
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
88 Chapter 2. Object-Oriented Programming
1 class Progression:
2 ”””Iterator producing a generic progression.
3
4 Default iterator produces the whole numbers 0, 1, 2, ...
5 ”””
6
7 def init (self, start=0):
8 ”””Initialize current to the first value of the progression.”””
9 self. current = start
10
11 def advance(self):
12 ”””Update self. current to a new value.
13
14 This should be overridden by a subclass to customize progression.
15
16 By convention, if current is set to None, this designates the
17 end of a finite progression.
18 ”””
19 self. current += 1
20
21 def next (self):
22 ”””Return the next element, or else raise StopIteration error.”””
23 if self. current is None: # our convention to end a progression
24 raise StopIteration( )
25 else:
26 answer = self. current # record current value to return
27 self. advance( ) # advance to prepare for next time
28 return answer # return the answer
29
30 def iter (self):
31 ”””By convention, an iterator must return itself as an iterator.”””
32 return self
Copyright © 2013. Wiley. All rights reserved.
33
34 def print progression(self, n):
35 ”””Print next n values of the progression.”””
36 print( .join(str(next(self)) for j in range(n)))
Code Fragment 2.8: A general numeric progression class.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.4. Inheritance 89
An Arithmetic Progression Class
11
12
13 def advance(self): # override inherited version
14 ”””Update current value by adding the fixed increment.”””
15 self. current += self. increment
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
90 Chapter 2. Object-Oriented Programming
A Geometric Progression Class
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.4. Inheritance 91
1 class FibonacciProgression(Progression):
2 ”””Iterator producing a generalized Fibonacci progression.”””
3
4 def init (self, first=0, second=1):
5 ”””Create a new fibonacci progression.
6
7 first the first term of the progression (default 0)
8 second the second term of the progression (default 1)
9 ”””
10 super( ). init (first) # start progression at first
11 self. prev = second − first # fictitious value preceding the first
12
13 def advance(self):
14 ”””Update current value by taking sum of previous two.”””
15 self. prev, self. current = self. current, self. prev + self. current
Code Fragment 2.11: A class that produces a Fibonacci progression.
value to (second − first), the initial advancement will set the new current value to
first + (second − first) = second, as desired.
To complete our presentation, Code Fragment 2.12 provides a unit test for all of
our progression classes, and Code Fragment 2.13 shows the output of that test.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
92 Chapter 2. Object-Oriented Programming
if name == __main__ :
print( Default progression: )
Progression( ).print progression(10)
Default progression:
0123456789
Arithmetic progression with increment 5:
0 5 10 15 20 25 30 35 40 45
Arithmetic progression with increment 5 and start 2:
2 7 12 17 22 27 32 37 42 47
Geometric progression with default base:
1 2 4 8 16 32 64 128 256 512
Copyright © 2013. Wiley. All rights reserved.
Code Fragment 2.13: Output of the unit tests from Code Fragment 2.12.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.4. Inheritance 93
the missing abstract behaviors, the inherited concrete behaviors are well defined.
As a tangible example, the collections.Sequence abstract base class defines be-
haviors common to Python’s list, str, and tuple classes, as sequences that sup-
port element access via an integer index. More so, the collections.Sequence class
provides concrete implementations of methods, count, index, and contains
that can be inherited by any class that provides concrete implementations of both
len and getitem . For the purpose of illustration, we provide a sample
implementation of such a Sequence abstract base class in Code Fragment 2.14.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
94 Chapter 2. Object-Oriented Programming
34 return k
Code Fragment 2.14: An abstract base class akin to collections.Sequence.
This implementation relies on two advanced Python techniques. The first is that
we declare the ABCMeta class of the abc module as a metaclass of our Sequence
class. A metaclass is different from a superclass, in that it provides a template for
the class definition itself. Specifically, the ABCMeta declaration assures that the
constructor for the class raises an error.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.4. Inheritance 95
The second advanced technique is the use of the @abstractmethod decorator
immediately before the len and getitem methods are declared. That de-
clares these two particular methods to be abstract, meaning that we do not provide
an implementation within our Sequence base class, but that we expect any concrete
subclasses to support those two methods. Python enforces this expectation, by dis-
allowing instantiation for any subclass that does not override the abstract methods
with concrete implementations.
The rest of the Sequence class definition provides tangible implementations for
other behaviors, under the assumption that the abstract len and getitem
methods will exist in a concrete subclass. If you carefully examine the source code,
the implementations of methods contains , index, and count do not rely on any
assumption about the self instances, other than that syntax len(self) and self[j] are
supported (by special methods len and getitem , respectively). Support
for iteration is automatic as well, as described in Section 2.3.4.
In the remainder of this book, we omit the formality of using the abc module.
If we need an “abstract” base class, we simply document the expectation that sub-
classes provide assumed functionality, without technical declaration of the methods
as abstract. But we will make use of the wonderful abstract base classes that are
defined within the collections module (such as Sequence). To use such a class, we
need only rely on standard inheritance techniques.
For example, our Range class, from Code Fragment 2.6 of Section 2.3.5, is an
example of a class that supports the len and getitem methods. But that
class does not support methods count or index. Had we originally declared it with
Sequence as a superclass, then it would also inherit the count and index methods.
The syntax for such a declaration would begin as:
class Range(collections.Sequence):
on a loop used to search for the desired value. For our Range class, there is an
opportunity for a more efficient determination of containment. For example, it
is evident that the expression, 100000 in Range(0, 2000000, 100), should evalu-
ate to True, even without examining the individual elements of the range, because
the range starts with zero, has an increment of 100, and goes until 2 million; it
must include 100000, as that is a multiple of 100 that is between the start and
stop values. Exercise C-2.27 explores the goal of providing an implementation of
Range. contains that avoids the use of a (time-consuming) loop.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
96 Chapter 2. Object-Oriented Programming
ods of the PredatoryCreditCard class, and finally a single instance namespace for
a sample instance of the PredatoryCreditCard class. We note that there are two
different definitions of a function named charge, one in the CreditCard class, and
then the overriding method in the PredatoryCreditCard class. In similar fashion,
there are two distinct init implementations. However, process month is a
name that is only defined within the scope of the PredatoryCreditCard class. The
instance namespace includes all data members for the instance (including the apr
member that is established by the PredatoryCreditCard constructor).
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.5. Namespaces and Object-Orientation 97
function
John Bowman
function init
California Savings customer
function get customer
bank
get bank 5391 0375 9387 5309
function account
get account
1234.56 balance
function make payment
function 2500 limit
function get balance init apr
get limit function process month 0.0825
function
charge charge
function function
(a) (b) (c)
Figure 2.8: Conceptual view of three namespaces: (a) the class namespace for
CreditCard; (b) the class namespace for PredatoryCreditCard; (c) the instance
namespace for a PredatoryCreditCard object.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
98 Chapter 2. Object-Oriented Programming
Class Data Members
A class-level data member is often used when there is some value, such as a con-
stant, that is to be shared by all instances of a class. In such a case, it would
be unnecessarily wasteful to have each instance store that value in its instance
namespace. As an example, we revisit the PredatoryCreditCard introduced in Sec-
tion 2.4.1. That class assesses a $5 fee if an attempted charge is denied because
of the credit limit. Our choice of $5 for the fee was somewhat arbitrary, and our
coding style would be better if we used a named variable rather than embedding
the literal value in our code. Often, the amount of such a fee is determined by the
bank’s policy and does not vary for each customer. In that case, we could define
and use a class data member as follows:
class PredatoryCreditCard(CreditCard):
OVERLIMIT FEE = 5 # this is a class-level member
Nested Classes
It is also possible to nest one class definition within the scope of another class.
This is a useful construct, which we will exploit several times in this book in the
implementation of data structures. This can be done by using a syntax such as
class A: # the outer class
class B: # the nested class
...
In this case, class B is the nested class. The identifier B is entered into the name-
Copyright © 2013. Wiley. All rights reserved.
space of class A associated with the newly defined class. We note that this technique
is unrelated to the concept of inheritance, as class B does not inherit from class A.
Nesting one class in the scope of another makes clear that the nested class
exists for support of the outer class. Furthermore, it can help reduce potential name
conflicts, because it allows for a similarly named class to exist in another context.
For example, we will later introduce a data structure known as a linked list and will
define a nested node class to store the individual components of the list. We will
also introduce a data structure known as a tree that depends upon its own nested
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.5. Namespaces and Object-Orientation 99
node class. These two structures rely on different node definitions, and by nesting
those within the respective container classes, we avoid ambiguity.
Another advantage of one class being nested as a member of another is that it
allows for a more advanced form of inheritance in which a subclass of the outer
class overrides the definition of its nested class. We will make use of that technique
in Section 11.2.1 when specializing the nodes of a tree structure.
By default, Python represents each namespace with an instance of the built-in dict
class (see Section 1.2.3) that maps identifying names in that scope to the associated
objects. While a dictionary structure supports relatively efficient name lookups,
it requires additional memory usage beyond the raw data that it stores (we will
explore the data structure used to implement dictionaries in Chapter 10).
Python provides a more direct mechanism for representing instance namespaces
that avoids the use of an auxiliary dictionary. To use the streamlined representation
for all instances of a class, that class definition must provide a class-level member
named slots that is assigned to a fixed sequence of strings that serve as names
for instance variables. For example, with our CreditCard class, we would declare
the following:
class CreditCard:
slots = _customer , _bank , _account , _balance , _limit
In this example, the right-hand side of the assignment is technically a tuple (see
discussion of automatic packing of tuples in Section 1.9.3).
When inheritance is used, if the base class declares slots , a subclass must
also declare slots to avoid creation of instance dictionaries. The declaration
in the subclass should only include names of supplemental methods that are newly
introduced. For example, our PredatoryCreditCard declaration would include the
following declaration:
class PredatoryCreditCard(CreditCard):
slots = _apr # in addition to the inherited members
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
100 Chapter 2. Object-Oriented Programming
In the last case shown, notice that the existence of a charge function in the
PredatoryCreditCard class has the effect of overriding the version of that function
that exists in the CreditCard namespace. In traditional object-oriented terminol-
ogy, Python uses what is known as dynamic dispatch (or dynamic binding) to
determine, at run-time, which implementation of a function to call based upon the
type of the object upon which it is invoked. This is in contrast to some languages
that use static dispatching, making a compile-time decision as to which version of
a function to call, based upon the declared type of a variable.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.6. Shallow and Deep Copying 101
color color
249 red 169 red
124 green 163 green
43 blue 52 blue
Unfortunately, this does not meet our desired criteria, because if we subsequently
add or remove colors from “palette,” we modify the list identified as warmtones.
We can instead create a new instance of the list class by using the syntax:
Copyright © 2013. Wiley. All rights reserved.
palette = list(warmtones)
In this case, we explicitly call the list constructor, sending the first list as a param-
eter. This causes a new list to be created, as shown in Figure 2.10; however, it is
what is known as a shallow copy. The new list is initialized so that its contents are
precisely the same as the original sequence. However, Python’s lists are referential
(see page 9 of Section 1.2.3), and so the new list represents a sequence of references
to the same elements as in the first.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
102 Chapter 2. Object-Oriented Programming
warmtones palette
list list
color color
249 red 169 red
124 green 163 green
43 blue 52 blue
This is a better situation than our first attempt, as we can legitimately add
or remove elements from palette without affecting warmtones. However, if we
edit a color instance from the palette list, we effectively change the contents of
warmtones. Although palette and warmtones are distinct lists, there remains indi-
rect aliasing, for example, with palette[0] and warmtones[0] as aliases for the same
color instance.
We prefer that palette be what is known as a deep copy of warmtones. In a
deep copy, the new copy references its own copies of those objects referenced by
the original version. (See Figure 2.11.)
warmtones palette
list list
the original color instances, but this requires that we know how to make copies of
colors (rather than aliasing). Python provides a very convenient module, named
copy, that can produce both shallow copies and deep copies of arbitrary objects.
This module supports two functions: the copy function creates a shallow copy
of its argument, and the deepcopy function creates a deep copy of its argument.
After importing the module, we may create a deep copy for our example, as shown
in Figure 2.11, using the command:
palette = copy.deepcopy(warmtones)
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.7. Exercises 103
2.7 Exercises
For help with exercises, please visit the site, www.wiley.com/college/goodrich.
Reinforcement
R-2.1 Give three examples of life-critical software applications.
R-2.2 Give an example of a software application in which adaptability can mean
the difference between a prolonged lifetime of sales and bankruptcy.
R-2.3 Describe a component from a text-editor GUI and the methods that it en-
capsulates.
R-2.4 Write a Python class, Flower, that has three instance variables of type str,
int, and float, that respectively represent the name of the flower, its num-
ber of petals, and its price. Your class must include a constructor method
that initializes each variable to an appropriate value, and your class should
include methods for setting the value of each type, and retrieving the value
of each type.
R-2.5 Use the techniques of Section 1.7 to revise the charge and make payment
methods of the CreditCard class to ensure that the caller sends a number
as a parameter.
R-2.6 If the parameter to the make payment method of the CreditCard class
were a negative number, that would have the effect of raising the balance
on the account. Revise the implementation so that it raises a ValueError if
a negative value is sent.
R-2.7 The CreditCard class of Section 2.3 initializes the balance of a new ac-
count to zero. Modify that class so that a new account can be given a
nonzero balance using an optional fifth parameter to the constructor. The
four-parameter constructor syntax should continue to produce an account
with zero balance.
R-2.8 Modify the declaration of the first for loop in the CreditCard tests, from
Copyright © 2013. Wiley. All rights reserved.
Code Fragment 2.3, so that it will eventually cause exactly one of the three
credit cards to go over its credit limit. Which credit card is it?
R-2.9 Implement the sub method for the Vector class of Section 2.3.3, so
that the expression u−v returns a new vector instance representing the
difference between two vectors.
R-2.10 Implement the neg method for the Vector class of Section 2.3.3, so
that the expression −v returns a new vector instance whose coordinates
are all the negated values of the respective coordinates of v.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
104 Chapter 2. Object-Oriented Programming
R-2.11 In Section 2.3.3, we note that our Vector class supports a syntax such as
v = u + [5, 3, 10, −2, 1], in which the sum of a vector and list returns
a new vector. However, the syntax v = [5, 3, 10, −2, 1] + u is illegal.
Explain how the Vector class definition can be revised so that this syntax
generates a new vector.
R-2.12 Implement the mul method for the Vector class of Section 2.3.3, so
that the expression v 3 returns a new vector with coordinates that are 3
times the respective coordinates of v.
R-2.13 Exercise R-2.12 asks for an implementation of mul , for the Vector
class of Section 2.3.3, to provide support for the syntax v 3. Implement
the rmul method, to provide additional support for syntax 3 v.
R-2.14 Implement the mul method for the Vector class of Section 2.3.3, so
that the expression u v returns a scalar that represents the dot product of
the vectors, that is, ∑di=1 ui · vi .
R-2.15 The Vector class of Section 2.3.3 provides a constructor that takes an in-
teger d, and produces a d-dimensional vector with all coordinates equal to
0. Another convenient form for creating a new vector would be to send the
constructor a parameter that is some iterable type representing a sequence
of numbers, and to create a vector with dimension equal to the length of
that sequence and coordinates equal to the sequence values. For example,
Vector([4, 7, 5]) would produce a three-dimensional vector with coordi-
nates <4, 7, 5>. Modify the constructor so that either of these forms is
acceptable; that is, if a single integer is sent, it produces a vector of that
dimension with all zeros, but if a sequence of numbers is provided, it pro-
duces a vector with coordinates based on that sequence.
R-2.16 Our Range class, from Section 2.3.5, relies on the formula
max(0, (stop − start + step − 1) // step)
to compute the number of elements in the range. It is not immediately ev-
ident why this formula provides the correct calculation, even if assuming
a positive step size. Justify this formula, in your own words.
R-2.17 Draw a class inheritance diagram for the following set of classes:
• Class Goat extends object and adds an instance variable tail and
methods milk( ) and jump( ).
Copyright © 2013. Wiley. All rights reserved.
• Class Pig extends object and adds an instance variable nose and
methods eat(food) and wallow( ).
• Class Horse extends object and adds instance variables height and
color, and methods run( ) and jump( ).
• Class Racer extends Horse and adds a method race( ).
• Class Equestrian extends Horse, adding an instance variable weight
and methods trot( ) and is trained( ).
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.7. Exercises 105
R-2.18 Give a short fragment of Python code that uses the progression classes
from Section 2.4.2 to find the 8th value of a Fibonacci progression that
starts with 2 and 2 as its first two values.
R-2.19 When using the ArithmeticProgression class of Section 2.4.2 with an in-
crement of 128 and a start of 0, how many calls to next can we make
before we reach an integer of 263 or larger?
R-2.20 What are some potential efficiency disadvantages of having very deep in-
heritance trees, that is, a large set of classes, A, B, C, and so on, such that
B extends A, C extends B, D extends C, etc.?
R-2.21 What are some potential efficiency disadvantages of having very shallow
inheritance trees, that is, a large set of classes, A, B, C, and so on, such
that all of these classes extend a single class, Z?
R-2.22 The collections.Sequence abstract base class does not provide support for
comparing two sequences to each other. Modify our Sequence class from
Code Fragment 2.14 to include a definition for the eq method, so
that expression seq1 == seq2 will return True precisely when the two
sequences are element by element equivalent.
R-2.23 In similar spirit to the previous problem, augment the Sequence class with
method lt , to support lexicographic comparison seq1 < seq2.
Creativity
C-2.24 Suppose you are on the design team for a new e-book reader. What are the
primary classes and methods that the Python software for your reader will
need? You should include an inheritance diagram for this code, but you
do not need to write any actual code. Your software architecture should
at least include ways for customers to buy new books, view their list of
purchased books, and read their purchased books.
C-2.25 Exercise R-2.12 uses the mul method to support multiplying a Vector
by a number, while Exercise R-2.14 uses the mul method to support
computing a dot product of two vectors. Give a single implementation of
Copyright © 2013. Wiley. All rights reserved.
Vector. mul that uses run-time type checking to support both syntaxes
u v and u k, where u and v designate vector instances and k represents
a number.
C-2.26 The SequenceIterator class of Section 2.3.4 provides what is known as a
forward iterator. Implement a class named ReversedSequenceIterator that
serves as a reverse iterator for any Python sequence type. The first call to
next should return the last element of the sequence, the second call to next
should return the second-to-last element, and so forth.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
106 Chapter 2. Object-Oriented Programming
C-2.27 In Section 2.3.5, we note that our version of the Range class has im-
plicit support for iteration, due to its explicit support of both len
and getitem . The class also receives implicit support of the Boolean
test, “k in r” for Range r. This test is evaluated based on a forward itera-
tion through the range, as evidenced by the relative quickness of the test
2 in Range(10000000) versus 9999999 in Range(10000000). Provide a
more efficient implementation of the contains method to determine
whether a particular value lies within a given range. The running time of
your method should be independent of the length of the range.
C-2.28 The PredatoryCreditCard class of Section 2.4.1 provides a process month
method that models the completion of a monthly cycle. Modify the class
so that once a customer has made ten calls to charge in the current month,
each additional call to that function results in an additional $1 surcharge.
C-2.29 Modify the PredatoryCreditCard class from Section 2.4.1 so that a cus-
tomer is assigned a minimum monthly payment, as a percentage of the
balance, and so that a late fee is assessed if the customer does not subse-
quently pay that minimum amount before the next monthly cycle.
C-2.30 At the close of Section 2.4.1, we suggest a model in which the CreditCard
class supports a nonpublic method, set balance(b), that could be used
by subclasses to affect a change to the balance, without directly accessing
the balance data member. Implement such a model, revising both the
CreditCard and PredatoryCreditCard classes accordingly.
C-2.31 Write a Python class that extends the Progression class so that each value
in the progression is the absolute value of the difference between the pre-
vious two values. You should include a constructor that accepts a pair of
numbers as the first two values, using 2 and 200 as the defaults.
C-2.32 Write a Python class that extends the Progression class so that each value
in the progression is the square root of the previous value. (Note that
you can no longer represent each value with an integer.) Your construc-
tor should accept an optional parameter specifying the start value, using
65, 536 as a default.
Copyright © 2013. Wiley. All rights reserved.
Projects
P-2.33 Write a Python program that inputs a polynomial in standard algebraic
notation and outputs the first derivative of that polynomial.
P-2.34 Write a Python program that inputs a document and then outputs a bar-
chart plot of the frequencies of each alphabet character that appears in
that document.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
2.7. Exercises 107
P-2.35 Write a set of Python classes that can simulate an Internet application in
which one party, Alice, is periodically creating a set of packets that she
wants to send to Bob. An Internet process is continually checking if Alice
has any packets to send, and if so, it delivers them to Bob’s computer, and
Bob is periodically checking if his computer has a packet from Alice, and,
if so, he reads and deletes it.
P-2.37 Write a simulator, as in the previous project, but add a Boolean gender
field and a floating-point strength field to each animal, using an Animal
class as a base class. If two animals of the same type try to collide, then
they only create a new instance of that type of animal if they are of differ-
ent genders. Otherwise, if two animals of the same type and gender try to
collide, then only the one of larger strength survives.
P-2.38 Write a Python program that simulates a system that supports the func-
tions of an e-book reader. You should include methods for users of your
system to “buy” new books, view their list of purchased books, and read
their purchased books. Your system should use actual books, which have
expired copyrights and are available on the Internet, to populate your set
of available books for users of your system to “purchase” and read.
P-2.39 Develop an inheritance hierarchy based upon a Polygon class that has
abstract methods area( ) and perimeter( ). Implement classes Triangle,
Quadrilateral, Pentagon, Hexagon, and Octagon that extend this base
Copyright © 2013. Wiley. All rights reserved.
class, with the obvious meanings for the area( ) and perimeter( ) methods.
Also implement classes, IsoscelesTriangle, EquilateralTriangle, Rectan-
gle, and Square, that have the appropriate inheritance relationships. Fi-
nally, write a simple program that allows users to create polygons of the
various types and input their geometric dimensions, and the program then
outputs their area and perimeter. For extra effort, allow users to input
polygons by specifying their vertex coordinates and be able to test if two
such polygons are similar.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.
108 Chapter 2. Object-Oriented Programming
Chapter Notes
For a broad overview of developments in computer science and engineering, we refer the
reader to The Computer Science and Engineering Handbook [96]. For more information
about the Therac-25 incident, please see the paper by Leveson and Turner [69].
The reader interested in studying object-oriented programming further, is referred to
the books by Booch [17], Budd [20], and Liskov and Guttag [71]. Liskov and Guttag
also provide a nice discussion of abstract data types, as does the survey paper by Cardelli
and Wegner [23] and the book chapter by Demurjian [33] in the The Computer Science
and Engineering Handbook [96]. Design patterns are described in the book by Gamma et
al. [41].
Books with specific focus on object-oriented programming in Python include those
by Goldwasser and Letscher [43] at the introductory level, and by Phillips [83] at a more
advanced level,
Copyright © 2013. Wiley. All rights reserved.
Goodrich, Michael T., et al. Data Structures and Algorithms in Python, Wiley, 2013. ProQuest Ebook Central, http://ebookcentral.proquest.com/lib/unimelb/detail.action?docID=4946360.
Created from unimelb on 2025-03-02 00:34:34.