The figure above is our compass: one call site (the amber button) fans out to many objects, and each object supplies its own method body. Keep this picture in mind — every exercise is a variation on it.
Goal: spot polymorphism, and spot when it is NOT happening.
Recall Solution L1.1
(a) YES — operator polymorphism. The same name + dispatches to str.__add__ (concatenate) and list.__add__ (concatenate lists). One symbol, two method bodies chosen by type(left).
(b) YES — duck typing.greet never names a class. It only needs x to have.speak(). Dog and Robot share no base class, yet both fit the implicit interface.
(c) YES — operator polymorphism (subtle!).* calls int.__mul__ on 3*2 → 6 and str.__mul__ on "hi"*2 → "hihi". Same operator, different behavior per type. Many students say "NO, it's just one function f" — but the polymorphism lives inside *, not in f.
Recall Solution L1.2
Line A works → "Quack". Line B raises AttributeError.
Why: at runtime noise executes a.quack(). Python searches quack along type(a).__mro__. For Dog, the MRO is [Dog, object] — no quack anywhere → AttributeError: 'Dog' object has no attribute 'quack'. Dogdoes have bark, but we never call bark, so it doesn't save us. The interface required is quack, and Dog doesn't satisfy it.
The figure above shows whytotal_area never changes: each shape holds its ownarea() body (cyan boxes), yet the loop calls them through the one shared interface. The amber labels are the different formulas — different maths, identical call s.area().
Recall Solution L2.3
Sum of .price attributes: 299+120+450=869. Prints 869.
total codes an interface (has .price), not a type — Book and Coffee are unrelated yet both fit. Add object() and the generator hits i.price on a plain object → AttributeError: 'object' object has no attribute 'price', raised the moment that item is reached. The lookup failed along type(i).__mro__ = [object].
Key insight: overriding in Dog changes only Dog's lookup. Cat and Animal are untouched. Each object resolves speak along its ownMethod Resolution Order (MRO).
Recall Solution L3.2
Assume Dog, Cat, Robot each define speak.
Version A (LBYL) returns 2 items (Dog, Cat). Robot fails the isinstance check and is silently dropped — even though it can speak! The type-check rejected a perfectly valid duck.
Version B (EAFP) returns 3 items (Dog, Cat, Robot). It just asks each object to speak; every object that has speak succeeds.
Why it matters: Version A must be edited every time a new speaker appears. Version B needs zero edits — this is EAFP preserving extensibility, the whole payoff of polymorphism.
Recall Solution L3.3
Dog() → has speak → appended. ✅
Cat() → has speak → appended. ✅
Robot() → has speak → appended. ✅
object() → no speak → AttributeError caught → skipped. ❌
Output: "$4.25". This is operator polymorphism you authored: the same + symbol now has meaning for Money because you supplied its __add__ body. See Dunder methods — __add__, __len__, __str__.
Recall Solution L4.2
len(obj) is polymorphic: it simply calls type(obj).__len__(obj). So:
len(Playlist([...3...])) → 3
len(Team([...2...])) → 2
Sum → 5.
Rule: len(x) ⟶ type(x).__len__(x); any object supplying __len__ becomes "measurable", no inheritance needed.
Recall Solution L4.3
def render_all(nodes): return sum(len(n.render()) for n in nodes)
It relies only on the render() interface — pure duck typing. Now the pieces:
Unwinding: "polygon: " + "shape" = "polygon: shape", then "square: " + "polygon: shape".
Final: "square: polygon: shape". This is override + super() cooperating along the MRO.
Recall Solution L5.2
chorus uses only the speak() interface, so Horn (no inheritance) joins the Animal subclasses seamlessly:
"Woof Meow Beep".
If Animal() itself is included, Animal.speak runs raise NotImplementedError → the whole chorus blows up with NotImplementedError. To prevent this at design time, make Animal a true abstract base class so it cannot be instantiated:
from abc import ABC, abstractmethodclass Animal(ABC): @abstractmethod def speak(self): ...
Bundle.cost calls p.cost() on each part. Thanks to polymorphism it doesn't care if p is an Item (returns its price) or a Bundle (recurses). Same call cost(), different body — a recursive composite tree.
Printed: 155. This composite pattern is polymorphism's endgame: uniform interface across a whole tree of different node types.
Recall One-line summary of the whole page
Every exercise reduces to one truth ::: x.method() resolves method along type(x).__mro__ at runtime — same call site, object-chosen body — so any object honoring the interface plugs in with zero caller changes.