Warm-up · Activity 1 of 7
Warm-up from module I1: you have written subclasses already. Every custom exception is one. What does this print?
class AppError(Exception):
pass
err = AppError("disk full")
print(isinstance(err, Exception), str(err))// I2.3 · ~35 min · Intermediate
After this lesson you can build a subclass that reuses and extends its base class, predict which method runs in a class hierarchy, and check types with isinstance and issubclass.
You will be able to
Warm-up · Activity 1 of 7
class AppError(Exception):
pass
err = AppError("disk full")
print(isinstance(err, Exception), str(err))Predict · Activity 2 of 7
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
def introduce(self):
return f"{self.name} says {self.speak()}"
class Dog(Animal):
def speak(self):
return "Woof"
print(Dog("Rex").introduce())Practice · Activity 3 of 7
class Employee:
def __init__(self, name):
self.name = name
class Manager(Employee):
def __init__(self, name, reports):
____.__init__(name)
self.reports = reportsPractice · Activity 4 of 7
class A:
def hello(self):
return "A"
class B(A):
pass
class C(A):
def hello(self):
return "C"
class D(B, C):
pass
print(D().hello(), [k.__name__ for k in D.__mro__])Practice · Activity 5 of 7
Brain teaser · Activity 6 of 7
class Base:
def setup(self):
return ["Base"]
class Logged(Base):
def setup(self):
return ["Logged"] + super().setup()
class Cached(Base):
def setup(self):
return ["Cached"] + super().setup()
class Service(Logged, Cached):
def setup(self):
return ["Service"] + super().setup()
print(Service().setup())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
send is written once, in Notifier, and calls self.format, which each subclass overrides. EmailNotifier extends __init__ with super(). SmsNotifier builds on the base format and shortens the text. LoudEmail inherits from two classes; its MRO puts Loud before EmailNotifier, so Loud's super().format reaches EmailNotifier, which adds the subject before Loud turns everything into capitals.
main.py
class Notifier:
"""Sends a message; subclasses decide how it looks."""
def __init__(self, recipient: str) -> None:
self.recipient = recipient
def format(self, text: str) -> str:
return text
def send(self, text: str) -> str:
return f"to {self.recipient}: {self.format(text)}"
class EmailNotifier(Notifier):
def __init__(self, recipient: str, subject: str) -> None:
super().__init__(recipient)
self.subject = subject
def format(self, text: str) -> str:
return f"[{self.subject}] {text}"
class SmsNotifier(Notifier):
LIMIT = 20
def format(self, text: str) -> str:
text = super().format(text)
return text if len(text) <= self.LIMIT else text[: self.LIMIT - 3] + "..."
class Loud(Notifier):
def format(self, text: str) -> str:
return super().format(text).upper()
class LoudEmail(Loud, EmailNotifier):
pass
notifiers: list[Notifier] = [
EmailNotifier("ada@example.com", "Invoice"),
SmsNotifier("0151-000000"),
LoudEmail("ops@example.com", "Alert"),
]
for notifier in notifiers:
print(notifier.send("Your order has shipped"))
print([cls.__name__ for cls in LoudEmail.__mro__])
print(isinstance(notifiers[2], EmailNotifier), issubclass(SmsNotifier, EmailNotifier))
Run it with
python main.pyOutput
to ada@example.com: [Invoice] Your order has shipped
to 0151-000000: Your order has sh...
to ops@example.com: [ALERT] YOUR ORDER HAS SHIPPED
['LoudEmail', 'Loud', 'EmailNotifier', 'Notifier', 'object']
True FalseTab 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
Book is a Product with an author. Complete it: its __init__ takes name, price and author, lets Product store name and price with super(), and stores author. Books use a VAT rate of 0.07 instead of 0.19: override the class variable VAT. label() of a book adds the author in parentheses to Product's label, for example Dune: 10.70 EUR (Herbert). Reuse Product.label with super().
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.
Start Book.__init__ with super().__init__(name, price); then set self.author.
Put VAT = 0.07 in the body of Book. price_with_tax reads self.VAT, which now finds Book's value first.
def label(self) -> str: return f"{super().label()} ({self.author})"
One way to solve it. Yours can look different and still pass the checks.
class Product:
VAT = 0.19
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def price_with_tax(self) -> float:
return round(self.price * (1 + self.VAT), 2)
def label(self) -> str:
return f"{self.name}: {self.price_with_tax():.2f} EUR"
class Book(Product):
VAT = 0.07
def __init__(self, name: str, price: float, author: str) -> None:
super().__init__(name, price)
self.author = author
def label(self) -> str:
return f"{super().label()} ({self.author})"
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 Product:
VAT = 0.19
def __init__(self, name: str, price: float) -> None:
self.name = name
self.price = price
def price_with_tax(self) -> float:
return round(self.price * (1 + self.VAT), 2)
def label(self) -> str:
return f"{self.name}: {self.price_with_tax():.2f} EUR"
class Book(Product):
def __init__(self, name: str, price: float, author: str) -> None:
self.author = author
test_main.py
from main import Book, Product
def test_is_product():
"""A Book is a Product"""
book = Book("Dune", 10.0, "Herbert")
assert isinstance(book, Product), "Book must derive from Product"
def test_base_attributes():
"""Book keeps name and price through super().__init__"""
book = Book("Dune", 10.0, "Herbert")
got = getattr(book, "name", None), getattr(book, "price", None), getattr(book, "author", None)
assert got == ("Dune", 10.0, "Herbert"), f"name, price and author are {got!r}"
def test_book_vat():
"""Books use 7% VAT"""
got = Book("Dune", 10.0, "Herbert").price_with_tax()
assert got == 10.7, f"price_with_tax() of a 10.00 book returned {got!r}, expected 10.7"
def test_product_unchanged():
"""Other products still use 19% VAT"""
got = Product("Mug", 10.0).price_with_tax()
assert got == 11.9, f"price_with_tax() of a 10.00 product returned {got!r}, expected 11.9"
def test_label():
"""A book's label ends with the author"""
got = Book("Dune", 10.0, "Herbert").label()
assert got == "Dune: 10.70 EUR (Herbert)", f"label() returned {got!r}"
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
Job(Validate, Save).run([]) should return ["validate", "save", "base"]: every class in the MRO adds its word once. First fix Save, which forgets to pass the log on with super(). Then write mro_names(cls), which returns the class names in cls.__mro__ without object, and count_steps(items), which counts the items that are instances of Step or any subclass of it.
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.
In Save.run, replace return log with return super().run(log), as Validate does.
cls.__mro__ is a tuple of classes; each has a __name__. Leave out the one that is object.
isinstance(item, Step) is True for Step and every subclass. sum(1 for item in items if ...) counts the matches.
One way to solve it. Yours can look different and still pass the checks.
class Step:
def run(self, log: list[str]) -> list[str]:
log.append("base")
return log
class Validate(Step):
def run(self, log: list[str]) -> list[str]:
log.append("validate")
return super().run(log)
class Save(Step):
def run(self, log: list[str]) -> list[str]:
log.append("save")
return super().run(log)
class Job(Validate, Save):
pass
def mro_names(cls: type) -> list[str]:
return [klass.__name__ for klass in cls.__mro__ if klass is not object]
def count_steps(items: list[object]) -> int:
return sum(1 for item in items if isinstance(item, Step))
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 Step:
def run(self, log: list[str]) -> list[str]:
log.append("base")
return log
class Validate(Step):
def run(self, log: list[str]) -> list[str]:
log.append("validate")
return super().run(log)
class Save(Step):
def run(self, log: list[str]) -> list[str]:
log.append("save")
return log
class Job(Validate, Save):
pass
def mro_names(cls: type) -> list[str]:
return []
def count_steps(items: list[object]) -> int:
return 0
test_main.py
from main import Job, Save, Step, Validate, count_steps, mro_names
def test_job_runs_every_step():
"""Job runs validate, save and base, once each"""
got = Job().run([])
assert got == ["validate", "save", "base"], f"Job().run([]) returned {got!r}"
def test_save_alone():
"""Save on its own also reaches the base step"""
got = Save().run([])
assert got == ["save", "base"], f"Save().run([]) returned {got!r}"
def test_mro_names():
"""mro_names lists the MRO without object"""
got = mro_names(Job)
assert got == ["Job", "Validate", "Save", "Step"], f"mro_names(Job) returned {got!r}"
def test_count_steps():
"""count_steps counts Step instances, subclasses included"""
got = count_steps([Job(), Save(), Step(), "save", 3, Validate])
assert got == 3, f"count_steps returned {got!r}, expected 3 (the class Validate itself is not an instance)"
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 Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
self.breed = breed
print(Dog("Rex", "collie").name)
What Python prints
AttributeError: 'Dog' object has no attribute 'name'Why, and the fix
Dog's __init__ overrides Animal's, so Animal.__init__ never runs and self.name is never set. Python does not call the base __init__ for you. Start the subclass __init__ with super().__init__(name), then add what is new.
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(self, name)
self.breed = breed
Dog("Rex", "collie")
What Python prints
TypeError: Animal.__init__() takes 2 positional arguments but 3 were givenWhy, and the fix
super() is already bound to the object, like self.method(), so the instance is passed for you. Passing self again adds one argument too many. Write super().__init__(name). Only the old form Animal.__init__(self, name), through the class, takes self explicitly.
class A:
pass
class B(A):
pass
class C(A, B):
pass
What Python prints
TypeError: Cannot create a consistent method resolution order (MRO) for bases A, BWhy, and the fix
C(A, B) asks for A before B, but B is a subclass of A and must come before it. No order satisfies both, so the class statement fails. List subclasses before their bases, class C(B, A), or simply class C(B), since B already brings A.
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.