05.2 - Inheritance & Polymorphism
Inheritance
Inheritance lets a class (child/subclass) inherit attributes and methods from another class (parent/superclass).
class Animal:
"""Base class for all animals."""
def __init__(self, name: str, sound: str):
self.name = name
self.sound = sound
def speak(self) -> str:
return f"{self.name} says: {self.sound}!"
def __str__(self) -> str:
return f"{self.__class__.__name__}({self.name})"
class Dog(Animal):
"""A dog that can fetch."""
def __init__(self, name: str, breed: str):
super().__init__(name, "Woof") # call parent __init__
self.breed = breed
def fetch(self, item: str) -> str:
return f"{self.name} fetches the {item}!"
class Cat(Animal):
def __init__(self, name: str, indoor: bool = True):
super().__init__(name, "Meow")
self.indoor = indoor
def purr(self) -> str:
return f"{self.name} purrs... 😸"
rex = Dog("Rex", "German Shepherd")
whiskers = Cat("Whiskers")
rex.speak() # Rex says: Woof!
rex.fetch("ball") # Rex fetches the ball!
whiskers.speak() # Whiskers says: Meow!
super() — Calling Parent Methods
super() calls the parent class method — essential in __init__ to initialize parent state:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def info(self):
return f"{self.year} {self.make} {self.model}"
class Car(Vehicle):
def __init__(self, make, model, year, doors=4):
super().__init__(make, model, year) # initialize Vehicle
self.doors = doors
def info(self):
base = super().info() # reuse parent method
return f"{base} ({self.doors} doors)"
class ElectricCar(Car):
def __init__(self, make, model, year, battery_kwh):
super().__init__(make, model, year)
self.battery_kwh = battery_kwh
def info(self):
base = super().info()
return f"{base} — {self.battery_kwh}kWh battery"
tesla = ElectricCar("Tesla", "Model 3", 2024, 82)
tesla.info() # 2024 Tesla Model 3 (4 doors) — 82kWh battery
Method Resolution Order (MRO)
Python uses the C3 linearization algorithm to determine method lookup order in multiple inheritance:
class A:
def hello(self): return "A"
class B(A):
def hello(self): return "B"
class C(A):
def hello(self): return "C"
class D(B, C):
pass
D.__mro__
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
D().hello() # "B" — follows MRO left-to-right
Polymorphism
Polymorphism means different classes respond to the same method call:
class Shape:
def area(self) -> float:
raise NotImplementedError
def describe(self):
return f"{self.__class__.__name__}: area = {self.area():.2f}"
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
import math
return math.pi * self.radius ** 2
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
# Polymorphic behavior — same interface, different implementations
shapes = [Circle(5), Rectangle(4, 6), Triangle(3, 8)]
for shape in shapes:
print(shape.describe()) # each calls its own area()
# Circle: area = 78.54
# Rectangle: area = 24.00
# Triangle: area = 12.00
Abstract Classes
Abstract classes define an interface that subclasses must implement:
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
"""Abstract base for all payment processors."""
@abstractmethod
def charge(self, amount: float) -> bool:
"""Charge the given amount. Must return True on success."""
...
@abstractmethod
def refund(self, amount: float) -> bool:
"""Issue a refund."""
...
def process(self, amount: float) -> str:
"""Template method — shared logic."""
if self.charge(amount):
return f"Payment of ${amount:.2f} processed"
return "Payment failed"
class StripeProcessor(PaymentProcessor):
def charge(self, amount):
print(f"Stripe: charging ${amount:.2f}")
return True
def refund(self, amount):
print(f"Stripe: refunding ${amount:.2f}")
return True
class PayPalProcessor(PaymentProcessor):
def charge(self, amount):
print(f"PayPal: charging ${amount:.2f}")
return True
def refund(self, amount):
print(f"PayPal: refunding ${amount:.2f}")
return True
# PaymentProcessor() # TypeError: Can't instantiate abstract class!
stripe = StripeProcessor()
stripe.process(99.99) # Stripe: charging $99.99 / Payment of $99.99 processed
isinstance() and issubclass()
tesla = ElectricCar("Tesla", "Model 3", 2024, 82)
isinstance(tesla, ElectricCar) # True
isinstance(tesla, Car) # True (is a Car)
isinstance(tesla, Vehicle) # True (is a Vehicle)
isinstance(tesla, Animal) # False
issubclass(ElectricCar, Car) # True
issubclass(ElectricCar, Vehicle) # True
issubclass(Dog, Vehicle) # False
Mixin Pattern
Mixins add specific capabilities without full inheritance:
class LogMixin:
def log(self, message):
print(f"[{self.__class__.__name__}] {message}")
class SerializeMixin:
def to_dict(self):
return {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
class User(LogMixin, SerializeMixin):
def __init__(self, name, email):
self.name = name
self.email = email
def activate(self):
self.log(f"Activating user {self.name}")
user = User("Alice", "alice@example.com")
user.activate() # [User] Activating user Alice
user.to_dict() # {'name': 'Alice', 'email': 'alice@example.com'}
Key Vocabulary
| Term | Definition |
|---|---|
| Inheritance | Child class acquires attributes/methods from parent class |
super() | Calls the parent class method |
| Method overriding | Redefining a parent method in a child class |
| Polymorphism | Different classes respond to the same method call |
| Abstract class | Class with abstract methods that must be implemented by subclasses |
ABC | Abstract Base Class from abc module |
@abstractmethod | Forces subclasses to implement a method |
| MRO | Method Resolution Order — determines method lookup in inheritance |
| Mixin | A class providing optional functionality via multiple inheritance |
Summary
- Inheritance:
class Child(Parent)— child gets all parent methods and attributes - Always call
super().__init__(...)in child__init__to initialize parent state - Method overriding: redefine a parent method in the child class
- Polymorphism: write code against an interface, let objects decide behavior
- Abstract classes (
ABC+@abstractmethod) enforce contracts on subclasses - The MRO determines method lookup order in multiple inheritance (C3 algorithm)
📄️ 05.1 - Classes & Objects
Define Python classes with __init__, instance methods, class variables, static methods, and properties
📄️ 05.2 - Inheritance & Polymorphism
Design class hierarchies using inheritance, method overriding, super(), abstract classes, and polymorphism
📄️ 05.3 - Dunder Methods
Control object behavior with Python's special methods: __repr__, __eq__, __len__, __add__, __iter__, and more
📄️ Lab - Module 05
Build a complete OOP bank account system with inheritance, properties, dunder methods, and abstract classes
📄️ Quiz - Module 05
30 questions on classes, inheritance, polymorphism, and dunder methods