Exercises — `super()` — calling parent methods
The core rule we lean on everywhere below, in plain words:
The picture that makes MRO tracing physical:

Level 1 — Recognition
Exercise 1.1
Which of these calls is written correctly inside Dog.__init__, given Animal.__init__(self, name)?
# (a) super().__init__(self, name)
# (b) super().__init__(name)
# (c) super.__init__(name)
# (d) Animal.__init__(name)Recall Solution 1.1
Answer: (b).
- (a) passes
selftwice — the proxy already bound it →TypeError: got multiple values for argument 'name'(or forself). - (c)
superwithout()is the class objectsuper, not a proxy —super.__init__is meaningless here. - (d)
Animal.__init__(name)calls the raw function withnamesitting in theselfslot — soselfbecomes the string"Rex". Wrong and dangerous. - (b) passes only the remaining argument. Correct.
Exercise 1.2
For class Dog(Animal): pass with class Animal: pass, what is Dog.__mro__?
Recall Solution 1.2
(Dog, Animal, object). Every class ultimately inherits from object, so it is always the last entry.
Level 2 — Application
Exercise 2.1
What does this print?
class Vehicle:
def __init__(self, wheels):
self.wheels = wheels
class Car(Vehicle):
def __init__(self, brand):
super().__init__(4)
self.brand = brand
c = Car("Toyota")
print(c.wheels, c.brand)Recall Solution 2.1
Prints 4 Toyota.
Car("Toyota")callsCar.__init__withbrand="Toyota".super().__init__(4)steps to the next class afterCarin(Car, Vehicle, object)→Vehicle, and runsVehicle.__init__(self, 4), soself.wheels = 4.- Then
self.brand = "Toyota".
Exercise 2.2
Extend a method (not just __init__). What is the return value and printed output?
class Base:
def price(self):
return 100
class Taxed(Base):
def price(self):
base = super().price()
return base * 1.1 # add 10% tax
print(Taxed().price())Recall Solution 2.2
Prints 110.0.
super().price()runsBase.price(self)→ returns100.100 * 1.1 = 110.0(a float, because1.1is a float).- Note the pattern: capture the parent result in a variable, then augment it. This is "extend, don't replace."
Level 3 — Analysis
Exercise 3.1
Trace the printed output for this diamond.
class A:
def __init__(self): print("A"); super().__init__()
class B(A):
def __init__(self): print("B"); super().__init__()
class C(A):
def __init__(self): print("C"); super().__init__()
class D(B, C):
def __init__(self): print("D"); super().__init__()
D()Recall Solution 3.1
Prints D B C A (each once), then stops.
- First write the MRO:
D.__mro__ = (D, B, C, A, object). See the figure below — it is a single straight line, not the diamond shape you drew. D.__init__: printsD,super()→ next afterDisB.B.__init__: printsB,super()→ next after B in D's MRO isC(notA!). This is the subtle point.C.__init__: printsC,super()→ next afterCisA.A.__init__: printsA,super()→ next afterAisobjectwhose__init__does nothing.

Exercise 3.2
What breaks here, and why?
class A:
def __init__(self): print("A"); super().__init__()
class B(A):
def __init__(self): print("B"); A.__init__(self) # hard-coded!
class C(A):
def __init__(self): print("C"); super().__init__()
class D(B, C):
def __init__(self): print("D"); super().__init__()
D()Recall Solution 3.2
Prints D B A — and C is silently SKIPPED.
D→B. Good.BprintsB, then callsA.__init__(self)directly — jumping straight toA, bypassingC.AprintsA, itssuper().__init__()reachesobject, done.C.__init__never runs. IfC.__init__set up an attribute, that attribute is missing → laterAttributeError. This is the exact bug the parent note warns about: hard-codedParent.__init__breaks the MRO chain.
Level 4 — Synthesis
Exercise 4.1
Design cooperative mixins so that saving passes through both layers exactly once, in order. Fill in the blanks and give the printed output.
class Saver:
def save(self, data):
print("write", data)
class Encrypted(Saver):
def save(self, data):
print("encrypt", data)
super().save(data) # (1)
class Compressed(Saver):
def save(self, data):
print("compress", data)
super().save(data) # (2)
class Store(Encrypted, Compressed):
pass
Store().save("hi")Predict the four printed lines.
Recall Solution 4.1
Prints:
encrypt hi
compress hi
write hi
- MRO:
Store.__mro__ = (Store, Encrypted, Compressed, Saver, object). Storehas nosave, so lookup starts atEncrypted.save: printsencrypt, thensuper()→Compressed.Compressed.save: printscompress, thensuper()→Saver.Saver.save: printswrite, nosuper()call → chain ends.- Each layer runs once, in MRO order. This is the "cooperative super" pattern: every method does its bit, then delegates upward.
Exercise 4.2
Add a class AuditedStore(Store) that prints "audit" before everything else, reusing the existing chain. Show the four... five printed lines.
Recall Solution 4.2
class AuditedStore(Store):
def save(self, data):
print("audit", data)
super().save(data)Prints:
audit hi
encrypt hi
compress hi
write hi
MRO: (AuditedStore, Store, Encrypted, Compressed, Saver, object). Store has no save, so after AuditedStore the chain flows to Encrypted and continues exactly as before — we extended without touching any existing class.
Level 5 — Mastery
Exercise 5.1
Cooperative __init__ with arguments. Every class consumes the kwargs it understands and forwards the rest via super().__init__(**kwargs). What are p.name, p.role, p.level?
class Person:
def __init__(self, name, **kw):
self.name = name
super().__init__(**kw) # forwards nothing left → object
class Employee(Person):
def __init__(self, role, **kw):
self.role = role
super().__init__(**kw)
class Manager(Employee):
def __init__(self, level, **kw):
self.level = level
super().__init__(**kw)
p = Manager(level=3, role="lead", name="Ana")
print(p.name, p.role, p.level)Recall Solution 5.1
Prints Ana lead 3.
- MRO:
(Manager, Employee, Person, object). Manager.__init__(level=3, role="lead", name="Ana"): setsself.level = 3, thensuper().__init__(role="lead", name="Ana")→Employee.Employee.__init__: setsself.role = "lead", thensuper().__init__(name="Ana")→Person.Person.__init__: setsself.name = "Ana", thensuper().__init__()→object.__init__()with no leftover kwargs → does nothing.- Every attribute is set exactly once.
**kwis the trick that lets each level pluck its own argument and pass the rest along without knowing who is next.
Exercise 5.2
Predict the failure. Why does this raise, and at which line?
class Person:
def __init__(self, name):
self.name = name
super().__init__()
class Employee(Person):
def __init__(self, role, **kw):
self.role = role
super().__init__(**kw)
Employee(role="dev", name="Sam", extra="oops")Recall Solution 5.2
Raises TypeError inside Person.__init__.
Employee.__init__peels offrole, forwards{name:"Sam", extra:"oops"}.Person.__init__(self, name, ...)acceptsnamebut has no**kwto absorbextra.- So
Person.__init__is called with an unexpected keywordextra="oops"→TypeError: __init__() got an unexpected keyword argument 'extra'. - Lesson: in cooperative
__init__, either every class accepts**kw, or the top of the chain must consume all remaining kwargs. A stray/misspelled kwarg surfaces exactly here.
Exercise 5.3
Two-word verify. For class D(B, C) with B(A), C(A), A(object), is the MRO [D, B, C, A, object] or [D, B, A, C, object]? State the C3 reason in one sentence.
Recall Solution 5.3
[D, B, C, A, object].
C3 linearization forbids a class from appearing before all of its subclasses are placed: A is a base of both B and C, so A cannot come until both B and C are already in the list. Hence A is pushed after C, giving D B C A object.
Recall Feynman recap: one sentence per level
L1 — super() already holds self, so never pass it.
L2 — capture the parent result, then extend it.
L3 — super() follows type(self)'s MRO, so it can jump to a sibling.
L4 — cooperative super() lets each layer run once, in MRO order.
L5 — forward leftover arguments with **kw so every class picks what it needs.
Connections
- Parent: `super()` — calling parent methods
- Method Resolution Order (MRO) & C3 Linearization
- Multiple Inheritance & the Diamond Problem
- `__init__` — constructors
- Method Overriding vs Overloading
- Inheritance — extending classes
- Composition over Inheritance