Advanced OOP
🏗️ Advanced OOP
📜 Abstract Base Classes
An abstract base class (inheriting from ABC) can declare methods with @abstractmethod that have no real implementation — just a contract. Python enforces this at instantiation time: you literally cannot create a Shape() instance, and any subclass that fails to override area() can't be instantiated either. This is stronger than Course 2, Chapter 2's plain Shape base class (which just returned 0 and quietly allowed instantiation) — here, forgetting to implement area() is caught immediately, not silently wrong.
🧬 Multiple Inheritance
Unlike Course 2's single-parent inheritance, Python allows a class to inherit from multiple parents at once. When more than one parent defines the same method, Python needs a deterministic rule to decide which one actually runs — that rule is the MRO.
🔗 The Method Resolution Order (MRO)
The Method Resolution Order is the exact sequence Python searches through to find a method — itself, then each parent, left to right, following Python's C3 linearization algorithm. Duck.move() resolves to Flyer.move specifically because Flyer appears before Swimmer in class Duck(Flyer, Swimmer):. Calling Duck.__mro__ (or Duck.mro()) shows the exact order any method lookup will follow.
The classic "diamond problem" — two parent classes that both inherit from a common grandparent, each overriding the same method differently — is exactly what the MRO exists to resolve deterministically. But deterministic doesn't mean easy to read: as inheritance chains get deeper, predicting which method actually runs from the class definition alone gets genuinely hard. In real code, favor composition (an object holding a reference to another object, and delegating to it) over multiple inheritance wherever it achieves the same goal — it's usually far easier to reason about.
🧪 A Metaclasses Intro
Just as a class is a blueprint for creating objects, a metaclass is a blueprint for creating classes — every class in Python is itself an instance of a metaclass, and by default, that metaclass is type:
A metaclass hooks into the process of defining a class, not creating instances of it — this runs once, when Python processes the class Widget: statement itself. Metaclasses are genuinely rare to write in application code; they show up mainly inside frameworks (Django's ORM uses one to turn model class attributes into database fields automatically). Recognizing what a metaclass is matters more than needing to write your own — most Python code never does.
Inheritance Models: Go vs Kotlin vs Python
| Language | Approach |
|---|---|
| Go | No inheritance at all — interfaces (implicit satisfaction) plus struct embedding cover similar ground, with no diamond problem possible since there's no class hierarchy to create one. |
| Kotlin | Single inheritance only (one open class parent), but a class can implement multiple interfaces — Kotlin sidesteps the diamond problem by only allowing ambiguity between interface default methods, which it forces you to resolve explicitly with super<InterfaceName>. |
| Python | True multiple inheritance from multiple concrete classes, resolved deterministically via the MRO — more powerful, and correspondingly easier to misuse. |
Kotlin's "single class, multiple interfaces" model is a deliberate middle ground — it gets most of multiple inheritance's benefit while designing away the diamond problem's worst confusion, something Python's fuller model doesn't do for you automatically.
ABC / @abstractmethod
Enforces a contract — a class can't be instantiated until it implements every abstract method.
Multiple inheritance
class C(A, B): — inherits from more than one parent at once.
MRO
The deterministic order Python searches parents in — view it with Class.__mro__.
Metaclass
A "class of a class" — type by default; hooks into class creation itself.
💻 Coding Challenges
Challenge 1: Enforced Payment Interface
Write an abstract base class PaymentMethod(ABC) with an abstract method process(amount). Write a subclass CreditCard that implements it (just returning a confirmation string is fine). Prove that instantiating PaymentMethod() directly raises a TypeError.
Goal: Practice defining and enforcing an abstract base class contract.
Challenge 2: Trace the MRO
Given three classes A, B(A), and C(A), and a fourth class D(B, C), print D.__mro__ and, in a comment, explain in one sentence why A appears only once despite being an ancestor of both B and C.
Goal: Get hands-on with reading an actual MRO output for a real diamond-shaped hierarchy.
Challenge 3: Composition Over Multiple Inheritance
Rewrite this chapter's Duck(Flyer, Swimmer) example using composition instead — a Duck class that holds a Flyer instance and a Swimmer instance as attributes, with its own fly() and swim() methods that delegate to them.
Goal: Practice the composition alternative this chapter's warn-box recommends, and see how it avoids MRO ambiguity entirely.
🎯 What's Next
Next chapter: Iterators & Context Managers Deep Dive — building a custom iterator, contextlib, and __enter__/__exit__.