Advanced OOP
๐๏ธ Advanced OOP
๐ Abstract Base Classes
from abc import ABC, abstractmethod class Shape(ABC): @abstractmethod def area(self): pass # no implementation โ subclasses MUST provide one class Square(Shape): def __init__(self, side): self.side = side def area(self): return self.side ** 2 Shape() # TypeError: Can't instantiate abstract class Shape with abstract method area Square(4).area() # 16 โ fine, Square actually implements area()
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
class Flyer: def move(self): return "flying" class Swimmer: def move(self): return "swimming" class Duck(Flyer, Swimmer): # inherits from BOTH pass Duck().move() # "flying" โ Flyer is listed first, so it wins
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)
print(Duck.__mro__) # (<class 'Duck'>, <class 'Flyer'>, <class 'Swimmer'>, <class 'object'>)
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:
print(type(42)) # <class 'int'> โ 42 is an instance of int print(type(Duck)) # <class 'type'> โ Duck is an instance of type class LoudMeta(type): # a custom metaclass, inheriting from type def __new__(mcs, name, bases, namespace): print(f"Creating class: {name}") return super().__new__(mcs, name, bases, namespace) class Widget(metaclass=LoudMeta): # Widget's CREATION triggers LoudMeta.__new__ pass # Creating class: Widget โ printed the moment the class body finishes, not when Widget() is called
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__.