Chapter 4 · Objects and robust code
Inheritance and special methods
Step 1 of 22 · Concept
A subclass gets everything its parent has
By the end of this lesson, your classes build on each other and work with print, ==, sorted and +.
from account import Account
class Savings(Account):
def add_interest(self):
self.balance += self.balance * 5 // 100
account.pyholds lesson 15'sAccount. The import brings it in (lesson 8).class Savings(Account):makes a subclass: aSavingsis anAccount, with more.- It gets every method of
Account,__init__too.
Step 2 of 22 · Concept
super().__init__ runs the parent's
class Savings(Account):
def __init__(self, owner, rate):
super().__init__(owner)
self.rate = rate
def add_interest(self):
self.balance += self.balance * self.rate // 100
- A
Savingsneeds arate, so it has its own__init__, which replacesAccount's. super().__init__(owner)runsAccount's__init__on this object. It setsownerandbalance.- Then
self.rate = rateadds what is new.
Step 3 of 22 · Quiz
Predict the account
account.py holds lesson 15's Account. Its __str__ returns f"{self.owner}: {self.balance}". Type exactly
what this program prints.
from account import Account
class Savings(Account):
def __init__(self, owner, rate):
super().__init__(owner)
self.rate = rate
def add_interest(self):
self.balance += self.balance * self.rate // 100
s = Savings("Ada", 5)
s.deposit(200)
s.add_interest()
print(s)
print(s.rate)
Step 4 of 22 · Demo
See it run
Run it and compare with your answer. Open account.py to see the parent class.
Step 5 of 22 · Concept
A subclass can replace a method
def withdraw(self, amount):
if self.balance - amount < 10:
print("Keep 10 in savings")
return False
return super().withdraw(amount)
- In
Savings, a method named like the parent's overrides it:s.withdraw(45)runs this one. super().withdraw(amount)still runsAccount's, which takes the money.
Step 6 of 22 · Demo
A withdrawal that keeps 10
Run it. Savings refuses the first withdraw; the second goes through to Account's.
isinstance(obj, Class) asks whether obj was made from Class or a subclass of it, as lesson 14's
isinstance(x, list) did. A Savings is an Account, so the last lines print True True, then False.
Step 7 of 22 · Quiz
Which one is False?
Gold is a subclass of Savings, which is a subclass of Account. Which of these is False?
class Gold(Savings):
def add_bonus(self):
self.balance += 10
g = Gold("Ada", 5)
s = Savings("Alan", 5)
Step 8 of 22 · Exercise
Your turn: a Checking account
Checking is an account that charges a fee on each withdrawal. Finish it:
Checking("Ada", 2)runsAccount's__init__, then keeps thefee.withdraw(amount)takesamountplus the fee, throughAccount'swithdraw.
The program must print:
Ada: 28
Refused
Ada: 28
Run it first: deposit stops with an AttributeError about balance. Checking's __init__ never ran
Account's, so no balance was set.
Make super().__init__(owner) the first line of __init__. Then add a method withdraw(self, amount) that passes
amount plus self.fee to super().withdraw.
return super().withdraw(amount + self.fee)
Step 9 of 22 · Concept
Same method, different classes
shapes = [Circle(1), Square(3)]
for shape in shapes:
print(f"{shape.area():.2f}")
CircleandSquareare two classes; neither is a subclass of the other. Each has its ownareamethod.- The loop calls
shape.area()on each object, and each answers with its own class'sarea. - Python never asks for the class, only for the method. This is duck typing.
Step 10 of 22 · Quiz
Predict the areas
Type exactly what this program prints.
import math
class Circle:
def __init__(self, r):
self.r = r
def area(self):
return math.pi * self.r ** 2
class Square:
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
shapes = [Circle(1), Square(3)]
total = 0
for shape in shapes:
print(f"{shape.area():.2f}")
total += shape.area()
print(f"Total: {total:.2f}")
Step 11 of 22 · Demo
Shapes in a loop
Run it and compare with your answer.
Step 12 of 22 · Exercise
Your turn: a Triangle
Write the class Triangle. Triangle(4, 3) has a base of 4 and a height of 3; its area() is half of base
times height.
The loop needs no change. The program must print:
3.14
9.00
6.00
Total: 18.14
Square is the model: an __init__ that keeps the sizes, and an area method that returns a number.
__init__(self, base, height) sets two attributes. area(self) multiplies them and divides by 2.
return self.base * self.height / 2
Step 13 of 22 · Concept
__repr__ is how a list shows an object
def __repr__(self):
return f"Account('{self.owner}', {self.balance})"
print(a)uses__str__. A list shows each of its items with__repr__instead.- With this
__repr__,print([a, b])shows[Account('Ada', 10), Account('Alan', 0)]. - Without it, the list shows addresses:
[<__main__.Account object at 0x...>].
Step 14 of 22 · Concept
== on objects asks "the same object?"
class Box:
def __init__(self, cents):
self.cents = cents
a = Box(500)
b = Box(500)
a is bisFalse: two objects (lesson 10).- For your own class,
==asks "the same object?" too, until you define__eq__. Soa == bisFalse. __eq__(self, other)decides what equal means, such asreturn self.cents == other.cents.
Step 15 of 22 · Quiz
Predict the comparisons
Type exactly what this program prints.
class Box:
def __init__(self, cents):
self.cents = cents
class Money:
def __init__(self, cents):
self.cents = cents
def __eq__(self, other):
return self.cents == other.cents
a = Box(500)
b = Box(500)
print(a == b, a is b, a == a)
c = Money(500)
d = Money(500)
print(c == d, c is d)
Step 16 of 22 · Demo
See it run
Run it and compare with your answer.
Step 17 of 22 · Concept
Operators call special methods
A method with two underscores on each side is a special method: Python calls it for you.
| you write | Python calls | so that |
|---|---|---|
a < b | a.__lt__(b) | sorted can put the objects in order |
len(w) | w.__len__() | len() gives your object's size |
a + b | a.__add__(b) | + makes something new, often a new object |
__init__, __str__, __repr__ and __eq__ are special methods too.
Step 18 of 22 · Demo
Money that adds and sorts
Run it. print uses __repr__, + uses __add__, sorted uses __lt__, and len uses __len__.
Step 19 of 22 · Exercise
Your turn: a Vector
Vector(1, 2) is an arrow 1 step right and 2 up. Give the class three special methods:
+adds two vectors part by part: a newVector.==isTruewhen both parts are equal.- A list shows a vector as
Vector(4, 6).
The program must print:
Vector(4, 6)
True
[Vector(0, 1), Vector(4, 6)]
Money in Money that adds and sorts has two of the three: __add__ and __repr__. The Money of Predict
the comparisons has an __eq__.
__add__(self, other) returns a new Vector made of the two sums. __eq__ compares x with x and y with y,
joined by and.
return Vector(self.x + other.x, self.y + other.y)
Step 20 of 22 · Check yourself
Check yourself: a playlist
Write two classes. No shape this time.
Song("Blue", 200)keeps a title and a length in seconds.Playlist("Road trip")keeps a name and an empty list of songs.add(song)appends one.len(p)gives the number of songs.print(p)shows the name, the count and the total seconds.
The program must print:
3
Road trip: 3 songs, 625 seconds
Playlist is shaped like Wallet in Money that adds and sorts: a list attribute, a method that appends, and
__len__. Its __str__ adds up the songs' seconds in a loop.
Step 21 of 22 · Challenge · optional
Challenge: in a playlist
Optional.
Make in work on a playlist: "Gold" in p is True when a song has that title. The program must print:
True
False
in is an operator, so it calls a special method: x in p calls p.__contains__(x), which returns True or
False.
__contains__(self, title) loops over self.songs and returns True at the first song with that title. After
the loop, nothing matched.
if song.title == title:
Step 22 of 22 · Recap
Recap
- A subclass gets every method of its parent;
super()reaches the parent's. - A method with the parent's name overrides it.
isinstance(s, Account)isTruefor a subclass's object. - Duck typing: one loop calls
area()on objects of different classes. - A list shows objects with
__repr__. Without__eq__,==asks "the same object?". <,len()and+call__lt__,__len__and__add__.
Next: lesson 17 makes code fail well: exceptions.
Review
4 questions from this lesson, in a new order each time. Answer from memory: 3 of 4 right on the first try passes. On a day after you finish the lesson, a pass earns 15 XP.
The review needs JavaScript.
from account import Account
class Savings(Account):
def __init__(self, owner, rate):
super().__init__(owner)
self.rate = rate
def add_interest(self):
self.balance += self.balance * self.rate // 100
s = Savings("Ada", 5)
s.deposit(200)
s.add_interest()
print(s)
print(s.rate)
from account import Account
class Savings(Account):
def __init__(self, owner, rate):
super().__init__(owner)
self.rate = rate
def withdraw(self, amount):
if self.balance - amount < 10:
print("Keep 10 in savings")
return False
return super().withdraw(amount)
s = Savings("Ada", 5)
s.deposit(50)
s.withdraw(45)
s.withdraw(30)
print(s)
print(isinstance(s, Account), isinstance(s, Savings))
a = Account("Alan")
print(isinstance(a, Savings))
from account import Account
class Checking(Account):
def __init__(self, owner, fee):
self.fee = fee
c = Checking("Ada", 2)
c.deposit(50)
c.withdraw(20)
print(c)
c.withdraw(40)
print(c)
import math
class Circle:
def __init__(self, r):
self.r = r
def area(self):
return math.pi * self.r ** 2
class Square:
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
shapes = [Circle(1), Square(3)]
total = 0
for shape in shapes:
print(f"{shape.area():.2f}")
total += shape.area()
print(f"Total: {total:.2f}")
import math
class Circle:
def __init__(self, r):
self.r = r
def area(self):
return math.pi * self.r ** 2
class Square:
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
# write the class Triangle here
shapes = [Circle(1), Square(3), Triangle(4, 3)]
total = 0
for shape in shapes:
print(f"{shape.area():.2f}")
total += shape.area()
print(f"Total: {total:.2f}")
class Box:
def __init__(self, cents):
self.cents = cents
class Money:
def __init__(self, cents):
self.cents = cents
def __eq__(self, other):
return self.cents == other.cents
a = Box(500)
b = Box(500)
print(a == b, a is b, a == a)
c = Money(500)
d = Money(500)
print(c == d, c is d)
class Money:
def __init__(self, cents):
self.cents = cents
def __add__(self, other):
return Money(self.cents + other.cents)
def __lt__(self, other):
return self.cents < other.cents
def __repr__(self):
return f"Money({self.cents})"
class Wallet:
def __init__(self):
self.items = []
def add(self, money):
self.items.append(money)
def __len__(self):
return len(self.items)
print(Money(250) + Money(100))
prices = [Money(500), Money(120), Money(300)]
print(sorted(prices))
w = Wallet()
w.add(Money(5))
w.add(Money(20))
print(len(w))
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
v = Vector(1, 2) + Vector(3, 4)
print(v)
print(v == Vector(4, 6))
print([Vector(0, 1), v])
# write the classes Song and Playlist here
p = Playlist("Road trip")
p.add(Song("Blue", 200))
p.add(Song("Gold", 185))
p.add(Song("Rain", 240))
print(len(p))
print(p)
class Song:
def __init__(self, title, seconds):
self.title = title
self.seconds = seconds
class Playlist:
def __init__(self, name):
self.name = name
self.songs = []
def add(self, song):
self.songs.append(song)
p = Playlist("Road trip")
p.add(Song("Blue", 200))
p.add(Song("Gold", 185))
print("Gold" in p)
print("Jazz" in p)