10.1 (What’s Wrong with This Code?) What is wrong with the code in the following IPython session?
In [1]: try:
...: raise RuntimeError()
...: except Exception:
...: print('An Exception occurred')
...: except RuntimeError:
...: print('A RuntimeError occurred')
...:
An Exception occurred
10.2 (Account Class with Read-Only Properties) Modify Section 10.2.2’s Account
class to provide read-only properties for the name
and balance
. Rename the class attributes with single leading underscores. Re-execute Section 10.2.2’s IPython session to test your updated class. To show that name
and balance
are read-only, try to assign new values to them.
10.3 (Time
Class Enhancement) Modify Section 10.4.2’s Time
class to provide a read-only property universal_str
that returns a string representation of a Time
in 24-hour clock format with two digits each for the hour, minute and second, as in '22:30:00'
(for 10:30 PM) or '06:30:00'
(for 6:30 AM). Test your new read-only property.
10.4 (Modifying the Internal Data Representation of a Class)
Section 10.4.2’s Time
class represents the time as three integer values. Modify the class to store the time as the total number of seconds since midnight. Replace the _hour, _minute and _second attributes with one _total_seconds
attribute. Modify the bodies of the hour
, minute
and second
properties’ methods to get and set _total_seconds
. Re-execute Section 10.4’s IPython session using the modified Time
class to show that the updated class Time
is interchangeable with the original one.
10.5 (Duck Typing) Recall that with duck typing, objects of unrelated classes can respond to the same method calls if they implement those methods. In Section 10.8, you created a list containing a CommissionEmployee
and a SalariedCommissionEmployee
. Then, you iterated through it, displaying each employee’s string representation and earnings. Create a class SalariedEmployee
for an employee that gets paid a fixed weekly salary. Do not inherit from CommissionEmployee
or SalariedCommissionEmployee
. In class SalariedEmployee
, override method __repr__
and provide an earnings
method. Demonstrate duck typing by creating an object of your class, adding it to the list at the end of Section 10.8, then executing the loop to show that it properly processes objects of all three classes.
10.6 (Composition: A Circle
“Has a” Point
at Its Center) A circle has a point at its center. Create a class Point
that represents an (x-y) coordinate pair and provides x
and y
read-write properties for the attributes _x
and _y
. Include __init__
and __repr__
methods, and a move
method that receives x- and y-coordinate values and sets the Point
’s new location. Create a class Circle
that has as its attributes _radius
and _point
(a Point
that represents the Circle
’s center location). Include __init__
and __repr__
methods, and a move
method that receives x- and y-coordinate values and sets a new location for the Circle
by calling the composed Point
object’s move method. Test your Circle
class by creating a Circle
object, displaying its string representation, moving the Circle
and displaying its string representation again.
10.7 (Manipulating Dates and Times with Module datetime
) The Python Standard Library’s datetime
module contains a datetime
class for manipulating dates and times. The class provides various overloaded operators. Research class datetime
’s capabilities, then perform the following tasks:
Get the current date and time and store it in variable x
.
Repeat Part (a) and store the result in variable y
.
Display each datetime
object.
Display each datetime
object’s data attributes individually.
Use the comparison operators to compare the two datetime
objects.
Calculate the difference between y
and x
.
10.8 (Converting Data Class Objects to Tuples and Dictionaries) In some cases, you might want to treat data class objects as tuples or dictionaries. The dataclasses module provides functions astuple
and asdict
for this purpose. Research these functions, then create an object of this chapter’s Card
data class and use these functions to convert the Card
to a tuple and a dictionary. Display the results.
10.9 (Square
Class) Write a class that implements a Square
shape. The class should contain a side
property. Provide an __init__
method that takes the side length as an argument. Also, provide the following read-only properties:
perimeter
returns 4
× side
.
area
returns side
×
side
.
diagonal
returns the square root of the expression (2
×
side
2)
.
The perimeter
, area
and diagonal
should not have corresponding data attributes; rather, they should use side
in calculations that return the desired values. Create a Square
object and display its side
, perimeter
, area
and diagonal
properties’ values.
10.10 (Invoice
Class) Create a class called Invoice
that a hardware store might use to represent an invoice for an item sold at the store. An Invoice
should include four pieces of information as data attributes—a part number (a string), a part description (a string), a quantity of the item being purchased (an int
) and a price per item (a Decimal
). Your class should have an __init__
method that initializes the four data attributes. Provide a property for each data attribute. The quantity and price per item should each be non-negative—use validation in the properties for these data attributes to ensure that they remain valid. Provide a calculate_invoice
method that returns the invoice amount (that is, multiplies the quantity by the price per item). Demonstrate class Invoice
’s capabilities.
10.11 (Class Fraction
) The Python Standard Library module fractions
provides a Fraction
class that stores the numerator and denominator of a fraction, such as:
Research Fraction
’s capabilities, then demonstrate:
Adding two Fraction
s.
Subtracting two Fraction
s.
Multiplying two Fraction
s.
Dividing two Fraction
s.
Printing Fraction
s in the form a/b
, where a
is the numerator and b
is the denominator.
Converting Fraction
s to floating-point numbers with built-in function float
.
10.12 (Built-in Type complex
) Python supports complex numbers with the built-in type complex
. Research complex
’s capabilities, then demonstrate:
Adding two complex
numbers.
Subtracting two complex
numbers.
Printing complex
numbers.
Getting the real and imaginary parts of complex
numbers.
10.13 (doctest
) Create a script containing the following maximum
function:
def maximum(value1, value2, value3):
"""Return the maximum of three values."""
max_value = value1
if value2 > max_value:
max_value = value2
if value3 > max_value:
max_value = value3
return max_value
Modify the function’s docstring to define tests for calling function maximum
with three int
s, three float
s and three strings. For each type, provide three tests—one with the largest value as the first argument, one with the largest value as the second argument, one with the largest value as the third argument. Use doctest
to run your tests and confirm that all execute correctly. Next, modify the maximum
function to use < operators rather than > operators. Run your tests again to see which tests fail.
10.14 (Creating an Account
Data Class Dynamically) The dataclasses
module’s make_dataclass
function creates a data class dynamically from a list of strings that represent the data class’s attributes. Research function make_dataclass
, then use it to generate an Account
class from the following list of strings:
['account', 'name', 'balance']
Create objects of the new Account
class, then display their string representations and compare the objects with the ==
and !=
operators.
10.15 (Immutable Data Class Objects) Built-in types int
, float
, str
and tuple
are immutable. Data classes can simulate immutability by designating that objects of the class should be “frozen” after they’re created. Client code cannot assign values to the attributes of a frozen object. Research “frozen” data classes, then reimplement this chapter’s Complex
class as a “frozen” data class. Show that you cannot modify a Complex
object after you create it.
10.16 (Account
Inheritance Hierarchy) Create an inheritance hierarchy that a bank might use to represent customer bank accounts. All customers at this bank can deposit money into their accounts and withdraw money from their accounts. More specific types of accounts also exist. Savings accounts, for instance, earn interest on the money they hold. Checking accounts, on the other hand, don’t earn interest and charge a fee per transaction.
Start with class Account
from this chapter and create two subclasses SavingsAccount
and CheckingAccount
. A SavingsAccount
should also include a data attribute indicating the interest rate. A SavingsAccount
’s calculate_interest
method should return the Decimal
result of multiplying the interest rate by the account balance. SavingsAccount
should inherit methods deposit
and withdraw
without redefining them.
A CheckingAccount
should include a Decimal
data attribute that represents the fee charged per transaction. Class CheckingAccount
should override methods deposit
and withdraw
so that they subtract the fee from the account balance whenever either transaction is performed successfully. CheckingAccount
’s versions of these methods should invoke the base-class Account
versions to update the account balance. CheckingAccount
’s withdraw
method should charge a fee only if money is withdrawn (that is, the withdrawal amount does not exceed the account balance).
Create objects of each class and tests their methods. Add interest to the SavingsAccount
object by invoking its calculate_interest
method, then passing the returned interest amount to the object’s deposit
method.
10.17 (Nested Functions and Namespaces) Section 10.15 discussed namespaces and how Python uses them to determine which identifiers are in scope. We also mentioned the LEGB (local, enclosing, global, built-in) rule for the order in which Python searches for identifiers in namespaces. For each of the print
function calls in the following IPython session, list the namespaces that Python searches for print
’s argument:
In [1]: z = 'global z'
In [2]: def print_variables():
...: y = 'local y in print_variables'
...: print(y)
...: print(z)
...: def nested_function():
...: x = 'x in nested function'
...: print(x)
...: print(y)
...: print(z)
...: nested_function()
...:
In [3]: print_variables()
local y in print_variables
global z
x in nested function
local y in print_variables
global z
10.18 (Intro to Data Science: Time Series) Reimplement the Intro to Data Science section’s study using the Los Angeles Average January High Temperatures for 1985 through 2018, which can be found in the file ave_hi_la_jan_1895-2018.csv
located in the ch10
examples folder. How does the Los Angeles temperature trend compare to that of New York City?
10.19 (Project: Static Code Analysis with Prospector and MyPy) In Exercise 3.24, you used the prospector
static code analysis tool to check your code for common errors and suggested improvements. The prospector
tool includes support for checking variable annotations with the MyPy static code analysis tool. Research MyPy online. Write a script that creates objects of this chapter’s Card
data class. In the script, assign integers to a Card
’s face
and suit
string attributes. Then, use MyPy to analyze the script and see the warning messages that MyPy produces. For instructions on using MyPy with prospector
, see
https:/ / github.com/ PyCQA/ prospector/ blob/ master/ docs/ supported_tools.rst
10.20 (Project: Solitaire) Using classes Card
and DeckOfCards
from this chapter’s examples, implement your favorite solitaire card game.
10.21 (Project: Blackjack) Using the DeckOfCards
class from this chapter, create a simple Blackjack game. The rules of the game are as follows:
Two cards each are dealt to the dealer and the player. The player’s cards are dealt face up. Only one of the dealer’s cards is dealt face up.
Each card has a value. A card numbered 2 through 10 is worth its face value. Jacks, queens and kings each count as 10. Aces can count as 1 or 11—whichever value is more beneficial to the player (as we’ll soon see).
If the sum of the player’s first two cards is 21 (that is, the player was dealt a card valued at 10 and an ace, which counts as 11 in this situation), the player has “blackjack” and immediately wins the game—if the dealer does not also have blackjack, which would result in a “push” (or tie).
Otherwise, the player can begin taking additional cards one at a time. These cards are dealt face up, and the player decides when to stop taking cards. If the player “busts” (that is, the sum of the player’s cards exceeds 21), the game is over and the player loses. When the player is satisfied with the current set of cards, the player “stands” (that is, stops taking cards), and the dealer’s hidden card is revealed.
If the dealer’s total is 16 or less, the dealer must take another card; otherwise, the dealer must stand. The dealer must continue taking cards until the sum of the cards is greater than or equal to 17. If the dealer exceeds 21, the player wins. Otherwise, the hand with the higher point total wins. If the dealer and the player have the same point total, the game is a “push,” and no one wins.
An ace’s value for a dealer depends on the dealer’s other card(s) and the casino’s house rules. A dealer typically must hit for totals of 16 or less and must stand for 17 or more. For a “soft 17”—a total of 17 with one ace counted as 11—some casinos require the dealer to hit and some require the dealer to stand (we require the dealer to stand). Such a hand is known as a “soft 17” because taking another card cannot bust the hand.
Enable a player to interact with the game using the keyboard—'H'
means hit (take another card and 'S'
means stand (do not take another card). Display the dealer’s and player’s hands as card images using Matplotlib, as we did in this chapter.
10.22 (Project: Card
Class with Overloaded Comparison Operators) Modify class Card
to support the comparison operators, so you can determine whether one Card
is less than, equal to or greater than another. Investigate the functools
module’s total_ordering
decorator. If your class is preceded by @total_ordering
and defines methods __eq__
and __lt__
(for the <
operator), the remaining comparison methods for <=
, >
and >=
are autogenerated.
10.23 (Project: Poker) Exercises 5.25–5.26 asked you to create functions for comparing poker hands. Develop equivalent features for use with this chapter’s DeckOfCards
class. Develop a new class called Hand
that represents a five-card poker hand. Use operator overloading to enable two Hand
s to be compared with the comparison operators. Use your new capabilities in a simple poker game script.
10.24 (Project: PyDealer Library) We demonstrated basic card shuffling and dealing in this chapter, but many card games require significant additional capabilities. As is often the case in Python, libraries already exist that can help you build more substantial card games. One such library is PyDealer. Research this library’s extensive capabilities, then use it to implement your favorite card game.
10.25 (Project: Enumerations) Many programming languages provide a language element called an enumeration for creating sets of named constants. Often, these are used to make code more readable. The Python Standard Library’s enum
module enables you to emulate this concept by creating subclasses of the Enum
base class. Investigate the enum
module’s capabilities, then create subclasses of Enum
that represent card faces and card suits. Modify class Card
to use these to represent the face and suit as enum
constants rather than as strings.
10.26 (Software Engineering with Abstract Classes and Abstract Methods) When we think of a class, we assume that programs use it to create objects. Sometimes, it’s useful to declare classes for which you never instantiate objects, because in some way they are incomplete. As you’ll see, such classes can help you engineer effective inheritance hierarchies.
Concrete Classes—Consider Section 10.7’s Shape
hierarchy. If Circle
, Square
and Triangle
objects all have draw
methods, its reasonable to expect that calling draw
on a Circle
will display a Circle
, calling draw
on a Square
will display a Square
and calling draw
on a Triangle
will display a Triangle
. Objects of each class know all the details of the specific shapes to draw. Classes that provide (or inherit) implementations of every method they define and that can be used to create objects are called concrete classes.
Abstract Classes—Now, let’s consider class TwoDimensionalShape
in the Shape
hierarchy’s second level. If we were to create a TwoDimensionalShape
object and call its draw
method, class TwoDimensionalShape
knows that all two-dimensional shapes are drawable, but it does not know what specific two-dimensional shape to draw—there are many! So it does not make sense for TwoDimensionalShape
to fully implement a draw
method. A method that is defined in a given class, but for which you cannot provide an implementation is called an abstract method. Any class with an abstract method has a “hole”—the incomplete method implementation—and is called an abstract class. TypeError
s occur when you try to create objects of abstract classes. In the Shape
hierarchy, classes Shape
, TwoDimensionalShape
and ThreeDimensionalShape
all are abstract classes. They all know that shapes should be drawable, but do not know what specific shape to draw. Abstract base classes are too general to create real objects.
Inheriting a Common Design—An abstract class’s purpose is to provide a base class from which subclasses can inherit a common design, such as a specific set of attributes and methods. So, such classes often are called abstract base classes. In the Shape
hierarchy, subclasses inherit from the abstract base class Shape
the notion of what it means to be a Shape
—that is, common properties, such as location
and color
, and common behaviors, such as draw
, move
and resize
.
Polymorphic Employee Payroll System—Now, let’s develop an Employee
class hierarchy that begins with an abstract class, then use polymorphism to perform payroll calculations for objects of two concrete subclasses. Consider the following problem statement:
A company pays its employees weekly. The employees are of two types. Salaried employees are paid a fixed weekly salary regardless of the number of hours worked. Hourly employees are paid by the hour and receive overtime pay (1.5 times their hourly salary rate) for all hours worked in excess of 40 hours. The company wants to implement an app that performs its payroll calculations polymorphically.
Employee
Hierarchy Class Diagram—The following diagram shows the Employee
hierarchy. Abstract class Employee
represents the general concept of an employee. Subclasses SalariedEmployee
and HourlyEmployee
inherit from Employee
. Employee
is italicized by convention to indicate that it’s an abstract class. Concrete class names are not italicized:
Abstract Base Class Employee
—The Python Standard Library’s abc
(abstract base class) module helps you define abstract classes by inheriting from the module’s ABC
class. Your abstract base class Employee
class should declare the methods and properties that all employees should have. Each employee, regardless of the way his or her earnings are calculated, has a first name, a last name and a Social Security number. Also, every employee should have an earnings
method, but the specific calculation depends on the employee’s type, so you’ll make earnings
an abstract method that the subclasses must override. Your Employee
class should contain:
An __init__
method that initializes the first name, last name and Social Security number data attributes.
Read-only properties for the first name, last name and Social Security number data attributes.
An abstract method earnings
preceded by the abc
module’s @abstractmethod
decorator. Concrete subclasses must implement this method. The Python documentation says you should raise a NotImplementedError
in abstract methods.26
A __repr__
method that returns a string containing the first name, last name and Social Security number of the employee.
Concrete Subclass SalariedEmployee
—This Employee
subclass should override earnings
to return a SalariedEmployee
’s weekly salary. The class also should include:
An __init__
method that initializes the first name, last name, Social Security number and weekly salary data attributes. The first three of these should be initialized by calling base class Employee
’s __init__
method.
A read-write weekly_salary
property in which the setter
ensures that the property is always non-negative.
A __repr__
method that returns a string starting with 'SalariedEmployee:'
and followed by all the information about a SalariedEmployee
. This overridden method should call Employee
’s version.
Concrete Subclass HourlyEmployee
—This Employee
subclass should override earnings
to return an HourlyEmployee
’s earnings, based on the hours worked and wage per hour. The class also should include:
An __init__
method to initialize the first name, last name, Social Security number, hours and wages data attributes. The first name, last name and Social Security number should be initialized by calling base class Employee
’s __init__
method.
Read-write hours
and wages
properties in which the setter
s ensure that the hours are in range (0–168) and wage per hour is always non-negative.
A __repr__
method that returns a string starting with 'HourlyEmployee:'
and followed by all the information about a HourlyEmployee
. This overridden method should call Employee
’s version.
Testing Your Classes—In an IPython session, test your hierarchy:
Import the classes Employee
, SalariedEmployee
and HourlyEmployee
.
Attempt to create an Employee
object to see the TypeError
that occurs and prove that you cannot create an object of an abstract class.
Assign objects of the concrete classes SalariedEmployee
and HourlyEmployee
to variables, then display each employee’s string representation and earnings.
Place the objects into a list, then iterate through the list and polymorphically process each object, displaying its string representation and earnings.