Board contents
Text extracted from this public whiteboard for search and accessibility.
0 · Agenda (0:00)
py-intro · 4.5 Inheritance & Polymorphism
Live session · 2 hours · Matheion m75 materials
0:00–0:10 Warm-up & goals Where inheritance sits after classes
0:10–0:40 Override vs extend Coffee café · super() · two bugs
0:40–1:05 Polymorphism One call site · many forms · alarms
1:05–1:25 Nominal checks isinstance · type is · issubclass
1:25–1:50 Method Resolution Order (MRO) & diamond Predict · run · __mro__ · cooperative init
1:50–2:00 Practice & wrap Quiz prompts · takeaways
Three questions for today
1. Override or extend?
Does the parent's behaviour still belong in the result?
2. What is polymorphism?
Same call site · different object · different behaviour
3. Where does super() go?
Next in the MRO — not "my literal parent"
Instructor kit • REPL shared screen • This board as spine • m75 quiz at end • Leave diamond for prediction
Student exits with • extend vs override rule • isinstance vs type is • can read Class.__mro__ • keeps calling super() in diamonds
2 · Override vs extend (0:10–0:40)
Override vs extend — Harbor Café coffee
One question: does the parent's behaviour still belong in the final result?
class Coffee: def make(self) -> str: return "espresso"
class Cappuccino(Coffee): def make(self) -> str: return f"{super().make()} + steamed milk" # → espresso + steamed milk # EXTEND — keep parent work
class Mocha(Coffee): def make(self) -> str: return "chocolate + espresso + milk" # no super() # OVERRIDE — replace
Two directions of extending
POST-process base = super().make() return base + "…" (Cappuccino)
PRE-process if shots < 1: raise … base = super().make() return f"{base} x{shots}" (DoubleShot)
Two classic bugs (act these out)
Bug 1 — silent override super().make() # discarded! return "quiet coffee" Parent ran · result lost
Bug 2 — accidental extend Meant to replace, but called super() from habit → layered / confusing output
Rule of thumb YES → parent's work belongs → EXTEND with super() NO → parent's work is wrong → OVERRIDE · skip super()
__init__ is usually extend class Gauge(Meter): def __init__(self, label, max_v): super().__init__(label) self.max_value = max_v Forgetting super() = half-init
Live demo (8 min) 1. Type Coffee + both kids 2. print each .make() 3. Break SilentCoffee 4. Fix: base = super().make() 5. Gauge __init__
3 · Polymorphism (0:40–1:05)
Polymorphism — one call site, many forms
"Poly-morphism" = many forms. The call looks the same; the object decides.
Key insight sound_off never asks "what kind of alarm?" No if / isinstance ladder. Just d.trigger().
Duck typing Python attribute dispatch cares about behaviour: if it has .trigger(), it runs. Declared type is optional.
Contrast later Nominal checks (isinstance) trust the *name* in the tree. Different tool · different job.
Smell to avoid if isinstance(a, X): … elif isinstance(a, Y): … elif isinstance(a, Z): … → prefer a method / singledispatch
Live ask Add BlinkAlarm with trigger → "flash". Does sound_off change? (No — that's the point.)
Optional: ABC peek from abc import ABC, abstractmethod class Alarm(ABC): @abstractmethod def trigger(self): ... Can't instantiate until subclass implements it. Recognition only today.
Checkpoint Can students explain: "same call, different object, different behaviour" in one sentence?
class Alarm: def trigger(self): raise NotImplementedError class LoudAlarm(Alarm): def trigger(self): return "BUZZ" class QuietAlarm(Alarm): def trigger(self): return "blink" def sound_off(devices): return [d.trigger() for d in devices] print(sound_off([LoudAlarm(), QuietAlarm()])) # ['BUZZ', 'blink']
1 · Warm-up (0:00–0:10)
Warm-up — inheritance in one picture
Ask: what do you already get "for free" from a parent class?
Coffee make() → espresso
Cappuccino extend + milk
Mocha override recipe
Live ask "If Cappuccino is a Coffee, what methods does it have before we write anything?"
Bridge from classes • attributes + methods • __init__ sets state • Today: reuse + specialise
Vocabulary
subclass class Child(Parent):
override same name · replace body · no super()
extend same name · call super() · add steps
polymorphism one call · many forms
MRO ordered list Python searches for a method
nominal checks trust declared hierarchy by name
5 · MRO (1:25–1:45)
MRO — method resolution order
super() means "next class in the MRO" — not "my literal parent".
Diamond — predict before you run
A
B
C
D
class D(B, C) MRO → (D, B, C, A, object) ping → D B C A After B, super() hops to C — not to A!
Swap parents class D(C, B) MRO → (D, C, B, A, object) output → D C B A Listed order is a contract.
How to inspect Klass.__mro__ Klass.mro() help(Klass)
Cooperative MI rule Every class in a diamond should call super() for chained methods / __init__. Skip once → silent holes.
Teaching move 1. Cover output 2. Students write prediction 3. Run · compare to __mro__ 4. Swap (C,B) · re-predict
Keep diamonds shallow Prefer mixins: small helpers, often no __init__. Deep webs = MRO surprises.
# Single inheritance — boring & clear class A: def ping(self): print("A") class B(A): def ping(self): print("B"); super().ping() class C(B): def ping(self): print("C"); super().ping() C().ping() # C B A print(C.__mro__) # (C, B, A, object)
4 · Nominal checks (1:05–1:25)
Nominal polymorphism — isinstance / type / issubclass
Nominal = name-based. These checks trust the declared class hierarchy.
Pick the right tool
isinstance(obj, Cls) "this class OR anything declared under it" Lenient · usual choice
type(obj) is Cls "exactly this class" Rejects subclasses on purpose
issubclass(A, B) About classes, not instances Hierarchy question
Trap: Object vs object object (lowercase) is the root of every class. Object → NameError isinstance("a", object) → True
Substitutability (LSP lite) Because Berry is declared under Fruit, hand a Berry anywhere a Fruit is expected. isinstance is meaningful only if subclasses keep promises.
Live drill (10 min) Predict each line before run. Then flip: invent ExactBerry check that rejects subclasses. When would you want that?
Good uses of isinstance • dispatch on declared family • one special-case fallback • accept Base + children Bad: growing if/elif ladder
Board vote b = Berry() isinstance(b, Fruit)? type(b) is Fruit? Hands up before reveal.
class Fruit: pass class Berry(Fruit): pass b = Berry() isinstance(b, Berry) # True isinstance(b, Fruit) # True ← subclasses count type(b) is Berry # True type(b) is Fruit # False ← exact class only issubclass(Berry, Fruit) # True
6 · Diamond lab (1:45–1:50)
Lab — cooperative __init__ through a diamond
Every class sets one field and calls super().__init__(). Watch the MRO order.
Break it on purpose Comment out super() in B. Re-run. Which fields vanish? Why?
Expected after break C never runs A never runs → no c, no a Silent · no exception That's the danger.
Also try Point3D.dump() extend pattern from m75 examples: data = super().dump() data.update({...}) return data
Time box: 5 min pair work then 2 min share
class A: def __init__(self): print("A init"); self.a = "a" class B(A): def __init__(self): print("B init"); super().__init__(); self.b = "b" class C(A): def __init__(self): print("C init"); super().__init__(); self.c = "c" class D(B, C): def __init__(self): print("D init"); super().__init__(); self.d = "d" d = D() print(d.__dict__) # D init → B init → C init → A init # {'a':'a','c':'c','b':'b','d':'d'}
7 · Practice & wrap (1:50–2:00)
Practice check + takeaways
Use Matheion quiz m75 (8 items). Attempt before Discuss / reveal.
Q1 subclass __init__ → usually super().__init__(...)
Q2 isinstance(Berry(), Fruit) → True
Q3 override = replace; extend = super() + add
Q4 device.trigger() many ways → polymorphism
Q5 type(Berry()) is Fruit → False (exact)
Q6 D(B,C) MRO → (D, B, C, A, object)
Q7 isinstance is nominal (declared names)
Q8 super().save() then log → extending
Exit tickets (say out loud)
1. One sentence: override vs extend
2. isinstance vs type(...) is — when?
3. What does super() mean in a diamond?
4. Why keep calling super() in __init__?
Homework / self-study • Type diamond · predict · run • Swap D(C,B) · re-predict • __init__ diamond · all fields • Convert one isinstance ladder → polymorphic method Material: m75 concept + examples
Quick reference
Quick reference
Child keeps parent result → EXTEND · call super() · use return value
Child prepares inputs first → EXTEND · validate · then super()
Parent behaviour is wrong → OVERRIDE · leave super() out
This type or under it → isinstance(obj, Cls)
Exactly this class → type(obj) is Cls
Is class under another? → issubclass(A, B)
See dispatch order → Klass.__mro__
Growing isinstance ladder → → method / singledispatch