Inheritance, Operators and Modules · Solo Project B Opens
Five problems on inheritance, special methods and module organization, then solo project B — arbitrary-precision arithmetic, where you have to build the machinery yourself.
Contents
What this session is for
- Organize a family of related classes with inheritance and polymorphism
- Make your classes fit Python’s syntax through special methods
- Split code into modules and guard the entry point with
if __name__ == "__main__": - Start solo project B
Problems
10-1 A family of shapes
Write a Shape base class and three subclasses:
class Shape:
def area(self) -> float: ... # base raises NotImplementedError
def name(self) -> str: ... # returns the class name
class Circle(Shape): def __init__(self, r: float) -> None: ...
class Rectangle(Shape): def __init__(self, w: float, h: float) -> None: ...
class Triangle(Shape): def __init__(self, a: float, b: float, c: float) -> None: ...
Use Heron’s formula for the triangle. Then a module-level function:
def total_area(shapes: list[Shape]) -> float: ...
10-2 Account inheritance
Building on L9’s BankAccount, write two subclasses:
SavingsAccount: adds aninterest_rateand anadd_interest()methodCheckingAccount: permits an overdraft down to-overdraft_limit
Both __init__ methods must use super().__init__(...), and CheckingAccount must override withdraw.
Answer in your notes: did you call super().withdraw() inside CheckingAccount.withdraw? Why or why not?
10-3 A Money class
Implement Money, storing the amount as an integer number of cents (L2 explained why money must not live in a float):
class Money:
def __init__(self, amount: float) -> None: ... # convert to cents
def __repr__(self) -> str: ... # e.g. Money(12.50)
def __add__(self, other: "Money") -> "Money": ...
def __sub__(self, other: "Money") -> "Money": ...
def __eq__(self, other: object) -> bool: ...
def __lt__(self, other: "Money") -> bool: ...
All of these must work:
Money(10.50) + Money(0.25) # Money(10.75)
sorted([Money(3), Money(1), Money(2)])
Money(0.1) + Money(0.2) == Money(0.3) # must be True
10-4 A container you can iterate
Implement a Deck class for a 52-card deck (no jokers):
class Deck:
def __init__(self) -> None: ...
def __len__(self) -> int: ...
def __getitem__(self, i: int) -> str: ...
def __contains__(self, card: str) -> bool: ...
def shuffle(self) -> None: ...
def deal(self, n: int) -> list[str]: ... # deal n cards, removing them
The card representation is yours to choose ("AH", "Ace of Hearts", whatever) — state it in your notes.
Once __getitem__ exists, all of these should work:
len(deck)
deck[0]
"AH" in deck
for card in deck: ...
10-5 Split it into modules
Reorganize 10-1 through 10-4 into three files:
shapes.py # 10-1
accounts.py # 10-2
cards.py # 10-3, 10-4
main.py # a demonstration importing the other three
Every module needs:
- a module docstring at the top
- an
if __name__ == "__main__":block holding that module’s self-tests
Then verify: running python3 main.py must not execute any of the three modules’ self-tests. Describe how you verified it in your notes.
Solo Project B: Arbitrary-Precision Integers
Worth 7.5% of the final grade Due Sunday, two weeks from now, 23:59
Background
L2 noted that Python integers have no upper limit — 2 ** 2000 simply works. Most languages cannot do that; their integers have a fixed width.
How does Python manage it? By storing the digits in an array and implementing the arithmetic itself. This project has you build that wheel once.
What to build
A BigInt class that must not use Python’s own big-integer arithmetic internally — you may not convert to int, add, and convert back.
class BigInt:
def __init__(self, value: str) -> None:
"""Construct from a decimal string, e.g. BigInt("123456789012345678901234567890").
A leading minus sign must be supported."""
def __repr__(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: object) -> bool: ...
def __lt__(self, other: "BigInt") -> bool: ...
def __add__(self, other: "BigInt") -> "BigInt": ...
def __sub__(self, other: "BigInt") -> "BigInt": ...
def __mul__(self, other: "BigInt") -> "BigInt": ...
Marks
| Component | Weight | Notes |
|---|---|---|
| addition | 20% | carries, and operands of different lengths |
| subtraction | 20% | borrows, and negative results |
| multiplication | 20% | long multiplication is fine; no advanced algorithms required |
comparison (__eq__, __lt__) | 10% | must sort correctly, negatives included |
| edge cases and robustness | 15% | see below |
| code quality | 5% | annotations, decomposition, naming |
| report | 10% | see below |
Edge cases you must handle
BigInt("0"), and operations whose result is zero- leading zeros:
BigInt("00123")must equalBigInt("123") - negatives: the sign rules for addition, subtraction and multiplication
- operands of different lengths
- subtraction producing a negative:
BigInt("5") - BigInt("8")
The report
At most one page, answering three questions:
- What is your internal representation? One digit per slot, or several? Why that choice?
- How did you handle signs? Stored separately, or per digit? How did you work out the sign rules for addition and subtraction?
- How did you test it? Name the specific edge cases you used, and say whether you cross-checked against Python’s
inton random inputs.
Before you submit
- Everything in 10-1 to 10-4 is annotated
- 10-3’s
Money(0.1) + Money(0.2) == Money(0.3)is True - 10-4’s
for card in deckworks - 10-5 verified that self-tests do not run on import
- Project B: all five categories of edge case tested
- Project B: the report answers all three questions
- AI usage declaration written