Advanced OOP

Python Advanced — Advanced OOP
Python Advanced
Course 3 · Chapter 1 · Advanced OOP

🏗️ Advanced OOP

Welcome to Python Advanced. Course 2 covered classes, inheritance, and dunder methods. This chapter pushes further into territory unique to Python's object system: abstract base classes that enforce a contract on subclasses, multiple inheritance and the Method Resolution Order that makes it deterministic, and a short, honest introduction to metaclasses — one of Python's more advanced (and rarely-needed) features.

📜 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.

⚠ Multiple Inheritance Gets Confusing Fast — Prefer Composition

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

LanguageApproach
GoNo 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.
KotlinSingle 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>.
PythonTrue 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.

→ Solution

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.

→ Solution

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.

→ Solution

🎯 What's Next

Next chapter: Iterators & Context Managers Deep Dive — building a custom iterator, contextlib, and __enter__/__exit__.