Warm-up · Activity 1 of 7
Warm-up from module B4: you have called methods on objects since the first modules. What does this print?
names = ["bob", "ada"]
names.sort()
print(names, "hi".upper())// I2.2 · ~30 min · Intermediate
After this lesson you can define a class with __init__ and methods, create instances that each keep their own data, and choose between instance and class variables.
You will be able to
Warm-up · Activity 1 of 7
names = ["bob", "ada"]
names.sort()
print(names, "hi".upper())Predict · Activity 2 of 7
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1
a = Counter()
b = Counter()
a.increment()
a.increment()
b.increment()
print(a.count, b.count)Practice · Activity 3 of 7
class Point:
def __init__(self, x, y):
____.x = x
self.y = yPractice · Activity 4 of 7
class Dog:
kind = "canine"
def __init__(self, name):
self.name = name
d = Dog("Fido")
e = Dog("Buddy")
e.kind = "robot"
print(d.kind, e.kind, Dog.kind)Practice · Activity 5 of 7
Brain teaser · Activity 6 of 7
class Dog:
tricks = []
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
d = Dog("Fido")
e = Dog("Buddy")
d.add_trick("roll over")
e.add_trick("play dead")
print(d.tricks)Apply · Activity 7 of 7
Check your work against this list
Read the worked example, then write the exercises. Your code runs in your browser or on your computer and is never uploaded.
Worked example
Each Cart keeps its owner and its own list of lines, created in __init__. The methods call each other through self: summary uses total, which uses net. vat_rate is a class variable, so changing it on the class changes it for every cart. The third line shows that a method call is the class's function with the instance passed as self.
main.py
class Cart:
"""A shopping cart that holds (name, price, quantity) lines."""
vat_rate = 0.19 # class variable: the same for every cart
def __init__(self, owner: str) -> None:
self.owner = owner
self.lines: list[tuple[str, float, int]] = [] # a new list for each cart
def add(self, name: str, price: float, quantity: int = 1) -> None:
self.lines.append((name, price, quantity))
def net(self) -> float:
return sum(price * quantity for _, price, quantity in self.lines)
def total(self) -> float:
return round(self.net() * (1 + self.vat_rate), 2)
def summary(self) -> str:
count = sum(quantity for _, _, quantity in self.lines)
return f"{self.owner}: {count} item(s), {self.total():.2f} EUR"
ada = Cart("ada")
ada.add("coffee", 8.50, 2)
ada.add("mug", 12.00)
bob = Cart("bob")
bob.add("tea", 4.20)
print(ada.summary())
print(bob.summary())
print(Cart.summary(bob) == bob.summary())
Cart.vat_rate = 0.07 # changes it for every cart
print(ada.summary())
Run it with
python main.pyOutput
ada: 3 item(s), 34.51 EUR
bob: 1 item(s), 5.00 EUR
True
ada: 3 item(s), 31.03 EURTab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.
Exercise 1 of 2
Complete the class Book. Book(title, pages) stores both and starts with current_page at 0. read(n) moves current_page forward by n pages, but never past the last page, and returns the new current_page. is_finished() returns True once current_page equals pages. Each book keeps its own progress.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.
Set self.current_page = 0 in __init__, next to title and pages.
In read, add n to self.current_page, and use min(..., self.pages) so it cannot pass the last page.
is_finished returns the comparison itself: return self.current_page == self.pages.
One way to solve it. Yours can look different and still pass the checks.
class Book:
def __init__(self, title: str, pages: int) -> None:
self.title = title
self.pages = pages
self.current_page = 0
def read(self, n: int) -> int:
"""Read n pages, stopping at the last page; return the current page."""
self.current_page = min(self.current_page + n, self.pages)
return self.current_page
def is_finished(self) -> bool:
return self.current_page == self.pages
Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.py
class Book:
def __init__(self, title: str, pages: int) -> None:
self.title = title
self.pages = pages
def read(self, n: int) -> int:
"""Read n pages, stopping at the last page; return the current page."""
return 0
def is_finished(self) -> bool:
return False
test_main.py
from main import Book
def test_starts_at_zero():
"""A new book starts at page 0"""
book = Book("Dune", 600)
got = getattr(book, "current_page", None)
assert got == 0, f"current_page of a new book is {got!r}, expected 0"
def test_read_moves_on():
"""read(n) adds n pages and returns the new page"""
book = Book("Dune", 600)
got = book.read(40), book.read(10)
assert got == (40, 50), f"read(40), read(10) returned {got!r}, expected (40, 50)"
def test_stops_at_last_page():
"""read never goes past the last page"""
book = Book("Emma", 100)
got = book.read(250)
assert got == 100, f"read(250) on a 100-page book returned {got!r}, expected 100"
def test_finished():
"""is_finished is True only at the last page"""
book = Book("Emma", 100)
book.read(99)
assert book.is_finished() is False, "is_finished() is True at page 99 of 100"
book.read(1)
assert book.is_finished() is True, "is_finished() is False at page 100 of 100"
def test_separate_books():
"""Two books keep separate progress"""
a = Book("A", 50)
b = Book("B", 50)
a.read(20)
assert b.read(5) == 5, "reading book a changed the progress of book b"
On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.
Run the program:
python main.pyRun the checks (needs learnrun.py in the same folder):
python learnrun.py testDownload learnrun.pyExercise 2 of 2
Every Ticket should get the next number: 1, 2, 3 and so on, counted in the class variable issued. Each ticket also has its own list of seats. The starter has two bugs: every ticket gets number 1, and all tickets share one seats list. Fix both, and keep issued on the class.
Tab indents and Shift+Tab outdents. To leave the editor with the keyboard, press Esc, then Tab.
The first run downloads Python for your browser (up to 6.5 MB) and keeps it cached. Your code stays on your device.
self.issued += 1 reads the class variable but then creates an instance attribute. Change the class variable through the class: Ticket.issued += 1.
A list in the class body is shared by every ticket. Create self.seats = [] in __init__ instead.
Remove the seats line from the class body, and add self.seats: list[str] = [] to __init__.
One way to solve it. Yours can look different and still pass the checks.
class Ticket:
issued = 0
def __init__(self, event: str) -> None:
self.event = event
Ticket.issued += 1
self.number = Ticket.issued
self.seats: list[str] = []
def add_seat(self, seat: str) -> None:
self.seats.append(seat)
Install Python 3.14 or newer. Save these files in one folder, open a terminal in that folder, and run the commands below.
main.py
class Ticket:
issued = 0
seats: list[str] = []
def __init__(self, event: str) -> None:
self.event = event
self.issued += 1
self.number = self.issued
def add_seat(self, seat: str) -> None:
self.seats.append(seat)
test_main.py
from main import Ticket
def test_numbers_count_up():
"""Tickets are numbered 1, 2, 3"""
Ticket.issued = 0
got = [Ticket("concert").number for _ in range(3)]
assert got == [1, 2, 3], f"three tickets got the numbers {got!r}, expected [1, 2, 3]"
def test_class_counter():
"""issued on the class counts every ticket"""
Ticket.issued = 0
Ticket("play")
Ticket("play")
assert Ticket.issued == 2, f"Ticket.issued is {Ticket.issued!r} after two tickets, expected 2"
def test_own_seats():
"""Each ticket has its own list of seats"""
a = Ticket("film")
b = Ticket("film")
a.add_seat("A1")
b.add_seat("B7")
assert a.seats == ["A1"], f"a.seats is {a.seats!r}, expected ['A1']"
assert b.seats == ["B7"], f"b.seats is {b.seats!r}, expected ['B7']"
On macOS and Linux, type python3 wherever these commands say python, as in the first lesson.
Run the program:
python main.pyRun the checks (needs learnrun.py in the same folder):
python learnrun.py testDownload learnrun.pyclass Greeter:
def hello():
return "hello"
Greeter().hello()
What Python prints
TypeError: Greeter.hello() takes 0 positional arguments but 1 was givenWhy, and the fix
A method call always passes the instance as the first argument, so every method needs a parameter to receive it. Write def hello(self):. The error counts that hidden argument: the "1 was given" is the instance itself.
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * radius ** 2
print(Circle(2).area())
What Python prints
NameError: name 'radius' is not definedWhy, and the fix
radius was a parameter of __init__, and it disappeared when __init__ returned. What stays is the attribute self.radius. Inside a method, always reach the object's data through self: 3.14159 * self.radius ** 2.
class Circle:
def __init__(self, radius):
self.radius = radius
c = Circle()
What Python prints
TypeError: Circle.__init__() missing 1 required positional argument: 'radius'Why, and the fix
The arguments of Circle(...) are passed on to __init__, after self. __init__ needs a radius, so pass one: Circle(2). If a sensible default exists, give the parameter one instead: def __init__(self, radius=1.0):.
Python in the browser: Pyodide 314.0.7, MPL-2.0. Licence and source
5 questions, no hints. Score 80% or more to complete the lesson.
Finish every activity above to unlock the exit ticket.