The Real Pain of Code Duplication
Imagine engineering a transportation fleet management system. Your class catalog includes Car, Bus, and Truck. Each entity intrinsically shares:
Copying and pasting this identical logic across dozens of classes bloats your codebase and turns fixing a single bug into a multi-file maintenance nightmare. Inheritance offers an elegant architectural solution.
01 What is Inheritance?
In clear, definitive terms:
Also known as the Base Class or Superclass. Encapsulates universal, shared state and generic behaviors (e.g., Vehicle).
Also known as the Derived Class or Subclass. Inherits parental traits while extending unique, domain-specific capabilities (e.g., Car or ElectricCar).
02 The Hallmark of True Inheritance: The “is-a” Relationship
When is inheritance conceptually appropriate? Architecture guides us with a simple litmus test:
- • Car is a Vehicle
- • Dog is an Animal
- • Manager is an Employee
- • SavingsAccount is a BankAccount
- • Engine is NOT a Car (Car has an Engine)
- • Wheel is NOT a Car (Car has Wheels)
- • Salary is NOT an Employee (Employee has a Salary)
- • CPU is NOT a Computer (Computer has a CPU)
If the conceptual link is has-a (containment or aggregation), do not reach for inheritance! In those scenarios, Composition is the correct design pattern, which we will explore deeply in Lesson 08.
03 Basic Python Syntax & Structure
Python makes class inheritance exceptionally clear. Passing the parent class name in parentheses when defining the subclass establishes the relationship:
# 1. Base Class (Encapsulating universal capabilities)
class Vehicle:
def start(self):
print("Vehicle power initiated.")
def stop(self):
print("Vehicle stopped.")
# 2. Child Class (Subclass inheriting from Vehicle)
class Car(Vehicle):
def open_trunk(self):
print("Trunk opened successfully.")
# Usage:
my_car = Car()
my_car.start() # ➔ Output: Vehicle power initiated. (Inherited)
my_car.open_trunk() # ➔ Output: Trunk opened successfully. (Own capability)
my_car.stop() # ➔ Output: Vehicle stopped. (Inherited)
Notice that inside Car, we never re-implemented start() or stop(). Through inheritance, Car naturally acquired those operations from Vehicle.
04
State Inheritance & the Power of super()
Just like behaviors, state can also be inherited. When a child class introduces additional attributes of its own—such as a car's door count or battery capacity—it must delegate core initialization back to the parent constructor using Python's built-in super():
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
print(f"Vehicle initialized: {self.brand} {self.model}")
class Car(Vehicle):
def __init__(self, brand, model, doors=4):
# super() invokes Vehicle's __init__ to handle brand and model
super().__init__(brand, model)
# Child class initializes its own specific state
self.doors = doors
car = Car("Toyota", "Corolla", doors=4)
print(f"Brand: {car.brand}, Doors: {car.doors}")
# Output: Brand: Toyota, Doors: 4
05 Method Overriding: Specializing Generic Behaviors
Do all vehicles start the exact same way? A petrol car turns an ignition key to fire internal combustion, whereas an electric vehicle silently boots up high-voltage battery arrays.
class Vehicle:
def start(self):
print("Generic power system activated.")
class ElectricCar(Vehicle):
def start(self):
# We can extend the base logic rather than completely replacing it:
super().start()
# Specific electric car behavior:
print("Electric drivetrain online: high-voltage inverter ready in silent mode!")
ev = ElectricCar()
ev.start()
# Output:
# Generic power system activated.
# Electric drivetrain online: high-voltage inverter ready in silent mode!
This concept of overriding generic parent methods is the direct stepping stone to Lesson 07's superpower: Polymorphism.
06 Real-World Corporate Example: The Employee Hierarchy
In an enterprise human resources platform, every employee shares foundational properties (name, ID, base salary, clock-in routines). But software engineers write code, product designers construct prototypes, and engineering managers approve budgets.
Because shared corporate policies live inside Employee, updating payroll algorithms occurs in one centralized location, automatically cascading to all job roles without code duplication.
07 The Dark Side of Inheritance: The Fragile Base Class Problem
Inheritance is undeniably powerful, but senior architects treat it with extreme caution. The most notorious architectural hazard of inheritance is known as The Fragile Base Class Problem.
Hierarchies deeper than 2 or 3 layers turn code tracing into a debugging nightmare. Developers lose track of which class level defined which attribute or behavior.
08 Multiple Inheritance in Python & MRO
Many strict languages (like Java or C#) prohibit a class from inheriting from more than one concrete superclass. Python, however, explicitly supports Multiple Inheritance:
pass
What happens if both Car and Boat declare a method with the same name, such as move()? Which implementation takes precedence?
Python resolves this using an algorithm known as C3 Linearization to establish a deterministic Method Resolution Order (MRO). You can inspect any class's resolution path in your terminal using AmphibiousVehicle.__mro__.
09 When to Use vs. When to Avoid Inheritance
- • A clear, unquestionable is-a relationship exists (Car is a Vehicle).
- • Base methods are universally relevant to all derived subclasses.
- • Subclasses can safely substitute for the parent without altering correctness (Liskov Substitution).
- • The hierarchy is shallow (1 to at most 2 levels deep).
- • The relationship is actually has-a (Car has an Engine).
- • You are using inheritance purely to salvage 5 lines of shared code.
- • Subclasses throw errors or ignore half of the inherited methods.
- • The tree is getting deeply nested. Choose Composition instead.
Concept Check
Verify your mastery of Lesson 06 concepts
Question 1: Which of the following pairs demonstrates a genuine "is-a" relationship suitable for inheritance?
Question 2: What is the primary role of super().__init__() in Python?