Chapter 4 · Objects and robust code
Classes and objects
Step 1 of 21 · Concept
A class is a new kind of value
By the end of this lesson, you can write a class of your own: a bank account.
A dictionary can keep a record:
account = {"owner": "Ada", "balance": 0}
Nothing says which keys an account must have, or what you can do with one. A class describes a kind of thing
once: its data and its actions. Each object made from it is one such thing, as 15 is one int.
Step 2 of 21 · Concept
__init__ fills in a new object
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
a = Account("Ada")
class Account:starts the class. Its indented block describes every account.Account("Ada")makes a new object, then runs__init__withselfpointing at it.self.owner = ownergives the object an attribute: a name inside the object.- Then
apoints at the object. Calling the class makes it (some languages writenew; Python does not).
Step 3 of 21 · Quiz
Predict the attributes
Type exactly what this program prints.
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
a = Account("Ada")
print(a.owner, a.balance)
Step 4 of 21 · Demo
See it run
Run it and compare with your answer.
Step 5 of 21 · Concept
A method is a function in the class
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
self.balance += amount
a = Account("Ada")
a.deposit(5)
- A method of your own is a function defined inside a class. Its first parameter,
self, is an object. a.deposit(5)runsAccount.deposit(a, 5): the object before the dot becomesself, andamountis 5.self.balance += amountchanges that object's attribute.
Step 6 of 21 · Trace
self is the object you called it on
Press Next to follow the object. Watch where self points in each frame.
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
self.balance += amount
a = Account("Ada")
a.deposit(5)
print(a.balance)- Line 9 made a new, empty
Accountobject and called__init__. Itsselfpoints at the object. Line 3 is next. - Line 3 gave the object the attribute
owner,"Ada". Line 4 is next. - Line 4 added
balance, 0.__init__has no line left, so it returns to line 9. __init__'s frame is gone. Line 9 gave the object the namea. Line 10 is next.- Line 10 called
a.deposit(5). Indeposit's frame,selfandapoint at one object. Line 7 is next. - Line 7 made the object's
balance5.deposithas no line left, so it returns to line 10. deposit's frame is gone. Line 11 is next.- Line 11 printed
5. The program is finished, and the stack is empty.
Step 7 of 21 · Quiz
Where does self point?
Line 7 runs inside the call a.deposit(5). Where does self point?
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
self.balance += amount
a = Account("Ada")
a.deposit(5)
print(a.balance)Step 8 of 21 · Exercise
Your turn: withdraw
Finish the method withdraw(self, amount):
- When
amountis more than the balance, printRefusedand returnFalse. - Otherwise, take
amountfrom the balance and returnTrue.
The program must print:
True
Refused
False
30
withdraw reads and changes self.balance, as deposit does. It decides first, then acts.
An if compares amount with the balance. Its block prints Refused and returns False. The else block takes
the money with -= and returns True.
if amount > self.balance:
Step 9 of 21 · Concept
Each object has its own attributes
a = Account("Ada")
b = Account("Alan")
a.deposit(5)
- Each call
Account(...)makes a new object, with its ownownerandbalance. a.deposit(5)changes only the objectapoints at, becauseselfis that object.b's object keeps its balance of 0.
Step 10 of 21 · Quiz
Predict both balances
Type exactly what this program prints.
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
self.balance += amount
a = Account("Ada")
b = Account("Alan")
a.deposit(5)
b.deposit(20)
a.deposit(5)
print(a.owner, a.balance)
print(b.owner, b.balance)
Step 11 of 21 · Demo
See it run
Run it and compare with your answer.
Step 12 of 21 · Concept
Two names, one account
a = Account("Ada")
c = a
c.deposit(1)
print(a.balance)
This prints 1.
c = amakes no new object. It gives the same object a second name: two arrows, one account.c.deposit(1)changes that object, soa.balanceis 1 too.- Only a call such as
Account("Ada")makes a new account.
Step 13 of 21 · Quiz
What went wrong?
This program stops with a traceback. What went wrong?
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
a = Account("Ada")
print(a.balanse)
Traceback (most recent call last):
File "main.py", line 7, in <module>
print(a.balanse)
^^^^^^^^^
AttributeError: 'Account' object has no attribute 'balanse'. Did you mean: 'balance'?
Step 14 of 21 · Exercise
Your turn: transfer
Finish the function transfer(src, dst, amount), outside the class:
- Take
amountfromsrcwithwithdraw. - Only when that worked (it returned
True), give it todstwithdeposit.
The program must print:
Refused
Ada 30
Alan 20
src and dst point at two account objects, so you can call their methods: src.withdraw(amount).
withdraw returns True or False. Use its answer as an if's condition, and deposit inside that if.
if src.withdraw(amount):
Step 15 of 21 · Concept
__str__ decides what print shows
Without help, print(a) shows the class and the object's address in memory:
<__main__.Account object at 0x...>
The digits after 0x change from run to run. Define __str__ to choose the text yourself:
def __str__(self):
return f"{self.owner}: {self.balance}"
print(a)callsa.__str__()and prints the text it returns:Ada: 5.- An f-string such as
f"{a}"uses it too, and so doesstr(a). __str__must return the text, not print it.
Step 16 of 21 · Demo
See it run
Run it. All three lines use __str__.
Step 17 of 21 · Concept
_balance: private by agreement
A name that starts with _ says: this belongs to the class; do not touch it from outside. It is private.
class Account:
def __init__(self, owner):
self.owner = owner
self._balance = 0
def balance(self):
return self._balance
- Code outside the class calls
a.balance()and leavesa._balancealone. - Python does not stop you:
a._balance = 1000000runs. The_is a promise between programmers, not a lock.
Step 18 of 21 · Exercise
Your turn: a Book
Finish the class Book:
Book("Dune", 412)setstitleandpages, and abookmarkat 0.read(n)moves the bookmarknpages on, but never past the last page.__str__returns text such asDune: page 42 of 412.
The program must print:
Dune: page 42 of 412
Dune: page 412 of 412
__init__ sets three attributes, as Account's sets two. read changes one of them; __str__ returns text.
In read, add n to self.bookmark. Then an if: when it is past self.pages, set it back to self.pages.
if self.bookmark > self.pages:
Step 19 of 21 · Check yourself
Check yourself: a counter
Write the class Counter. No shape this time.
Counter("clicks")keeps the name and starts the count at 0.increment()adds 1;increment(by=5)adds 5.reset()sets the count back to 0.print(c)shows the name and the count, asclicks: 7.
The program must print:
clicks: 7
clicks: 1
Four methods: __init__, increment, reset and __str__. increment takes a second parameter after self, with a
default value, as functions do in lesson 7.
Step 20 of 21 · Challenge · optional
Challenge: count the accounts
Optional.
Count every account made. Keep the last four lines as they are, and make the program print:
Accounts: 3
A name set in the class's block, outside any method, belongs to the class itself: a class attribute, shared by
every object. Set count = 0 there.
Add 1 in __init__, through the class. Not through self: self.count += 1 makes a new attribute on the object
and leaves Account.count at 0.
Account.count += 1
Step 21 of 21 · Recap
Recap
Account("Ada")makes an object and runs__init__, which fills in its attributes.- Each object has its own attributes.
- A method gets the object before the dot as
self:a.deposit(5). c = agives one object two names. Only calling the class makes a new one.__str__returns the textprintshows; a leading_means private.
Next: lesson 16 builds kinds of account on this one, and teaches Python's special methods.
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.
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
a = Account("Ada")
print(a.owner, a.balance)
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
# refuse when amount is more than the balance
return False
a = Account("Ada")
a.deposit(50)
print(a.withdraw(20))
print(a.withdraw(100))
print(a.balance)
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
self.balance += amount
a = Account("Ada")
b = Account("Alan")
a.deposit(5)
b.deposit(20)
a.deposit(5)
print(a.owner, a.balance)
print(b.owner, b.balance)
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
self.balance += amount
def withdraw(self, amount):
if amount > self.balance:
print("Refused")
return False
else:
self.balance -= amount
return True
def transfer(src, dst, amount):
dst.deposit(amount)
a = Account("Ada")
b = Account("Alan")
a.deposit(50)
transfer(a, b, 20)
transfer(a, b, 40)
print(a.owner, a.balance)
print(b.owner, b.balance)
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
def deposit(self, amount):
self.balance += amount
def __str__(self):
return f"{self.owner}: {self.balance}"
a = Account("Ada")
a.deposit(5)
print(a)
print(f"Statement: {a}")
print(str(a).upper())
class Book:
def __init__(self, title, pages):
self.title = title
# also keep pages, and a bookmark at 0
def read(self, n):
# move the bookmark n pages on, not past the end
print("reading", n)
def __str__(self):
# text such as Dune: page 42 of 412
return self.title
book = Book("Dune", 412)
book.read(30)
book.read(12)
print(book)
book.read(500)
print(book)
# write the class Counter here
c = Counter("clicks")
c.increment()
c.increment()
c.increment(by=5)
print(c)
c.reset()
c.increment()
print(c)
class Account:
def __init__(self, owner):
self.owner = owner
self.balance = 0
a = Account("Ada")
b = Account("Alan")
c = Account("Grace")
print("Accounts:", Account.count)