Inheritance & Polymorphism
Checking access...
Method Resolution Order (MRO)
Python determines which method to call using MRO — a well-defined lookup order:
class A: def method(self): return "A"
class B(A): def method(self): return "B"
class C(A): def method(self): return "C"
class D(B, C): pass
d = D()print(d.method()) # B — follows MRO: D → B → C → Aprint(D.__mro__) # (D, B, C, A, object)Using super()
Call parent class methods:
class Person: def __init__(self, name, age): self.name = name self.age = age
class Employee(Person): def __init__(self, name, age, employee_id): super().__init__(name, age) # Call parent constructor self.employee_id = employee_id
def __str__(self): return f"{self.name} (ID: {self.employee_id})"
emp = Employee("Alice", 30, "E001")print(emp.name) # Alice — set by Person.__init__print(emp.employee_id) # E001 — set by Employee.__init__super() in Multiple Inheritance
class Base: def __init__(self): print("Base.__init__") super().__init__() # Calls next in MRO
class MixinA(Base): def __init__(self): print("MixinA.__init__") super().__init__()
class MixinB(Base): def __init__(self): print("MixinB.__init__") super().__init__()
class Concrete(MixinA, MixinB): def __init__(self): print("Concrete.__init__") super().__init__()
obj = Concrete()# Concrete.__init__# MixinA.__init__# MixinB.__init__# Base.__init__Polymorphism
Different classes providing the same interface:
from abc import ABC, abstractmethod
class PaymentProcessor(ABC): @abstractmethod def process_payment(self, amount): pass
@abstractmethod def refund(self, transaction_id): pass
class CreditCardProcessor(PaymentProcessor): def process_payment(self, amount): return f"Charged ${amount} to credit card"
def refund(self, transaction_id): return f"Refunded transaction {transaction_id}"
class PayPalProcessor(PaymentProcessor): def process_payment(self, amount): return f"Processed ${amount} via PayPal"
def refund(self, transaction_id): return f"PayPal refund {transaction_id} initiated"
# Polymorphic usagedef checkout(processor, amount): print(processor.process_payment(amount))
checkout(CreditCardProcessor(), 100)checkout(PayPalProcessor(), 50)Duck Typing
“If it walks like a duck and quacks like a duck, it’s a duck”:
class Duck: def quack(self): return "Quack!" def walk(self): return "Waddle"
class RobotDuck: def quack(self): return "Beep quack!" def walk(self): return "Mechanical waddle"
def make_it_quack(thing): # Doesn't care about type — just calls .quack() print(thing.quack())
make_it_quack(Duck()) # Quack!make_it_quack(RobotDuck()) # Beep quack!Abstract Base Classes
from abc import ABC, abstractmethod
class Shape(ABC): @abstractmethod def area(self): pass
@abstractmethod def perimeter(self): pass
# Concrete method — shared by all subclasses def describe(self): return f"Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}"
class Rectangle(Shape): def __init__(self, width, height): self.width = width self.height = height
def area(self): return self.width * self.height
def perimeter(self): return 2 * (self.width + self.height)
# shape = Shape() # TypeError! Can't instantiate abstract classrect = Rectangle(5, 3)print(rect.describe()) # Area: 15.00, Perimeter: 16.00Composition Over Inheritance
class Engine: def start(self): return "Engine started" def stop(self): return "Engine stopped"
class Wheels: def rotate(self): return "Wheels rotating"
class Car: """Uses composition — has-a relationship.""" def __init__(self): self.engine = Engine() # Composition self.wheels = [Wheels() for _ in range(4)]
def drive(self): return f"{self.engine.start()} — {self.wheels[0].rotate()}"
car = Car()print(car.drive()) # Engine started — Wheels rotatingMixins
Reusable pieces of functionality via multiple inheritance:
class JSONMixin: def to_json(self): import json return json.dumps(self.__dict__)
class LoggableMixin: def log(self, message): print(f"[{self.__class__.__name__}] {message}")
class User(JSONMixin, LoggableMixin): def __init__(self, name, email): self.name = name self.email = email
user = User("Alice", "alice@example.com")print(user.to_json()) # {"name": "Alice", "email": "alice@example.com"}user.log("User created")Key Takeaways
- MRO (Method Resolution Order) determines method lookup —
Class.__mro__shows it super()delegates to the next class in MRO — works correctly with multiple inheritance- Abstract Base Classes define interfaces that subclasses must implement
- Duck typing: if an object has the right methods, it works — no explicit interface needed
- Prefer composition (
has-a) over inheritance (is-a) for flexible design - Mixins provide reusable behavior via multiple inheritance