18 min read Core OOP Pillar Lesson 06 of 12
Course 04 · Part 2 — Core OOP Pillars

Inheritance — Building One Class from Another

Reusing shared characteristics to construct specialized object hierarchies

So far, we have mastered object state, behavior, encapsulation, and abstraction. But what happens when an application requires dozens of closely related entities—such as cars, buses, trucks, and motorcycles? Should you copy and paste the same foundational attributes and methods into every single file? The primary object-oriented mechanism for establishing taxonomic classification and eliminating duplication is Inheritance.

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:

brand, model, speed, start(), stop()

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:

Inheritance is an object-oriented mechanism wherein a new class (Child Class) automatically acquires the attributes and behaviors of an existing class (Parent Class), with the ability to add new properties or override existing behaviors.
Parent Class (Base / Superclass)

Also known as the Base Class or Superclass. Encapsulates universal, shared state and generic behaviors (e.g., Vehicle).

Child Class (Derived / Subclass)

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:

“Is this candidate object genuinely a specialized type of that other object? (Is A a B?)”
Valid "is-a" Relationships (Inheritance Fits)
  • • Car is a Vehicle
  • • Dog is an Animal
  • • Manager is an Employee
  • • SavingsAccount is a BankAccount
Invalid "is-a" but "has-a" (Use Composition Instead!)
  • • 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:

simple_vehicle.py Parent & Child Classes
# 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():

super_init.py Reusing Initialization Logic with 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
Constructor Execution Sequence
Car.__init__() ➔ super().__init__() ➔ Vehicle.__init__() sets brand & model ➔ Car sets doors

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.

Method Overriding occurs when: A child class redefines an inherited method using the exact same signature, substituting the parent's generic implementation with its own specialized logic.
overriding_demo.py Overriding and Extending Methods
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.

Organizational Class Hierarchy
Employee (Parent Class) name, emp_id, salary | clock_in(), get_pay()
Developer stack, git_repo write_code()
Designer figma_handle, tools design_ui()
Manager team_size, budget approve_budget()

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.

What is a Fragile Base Class? Child classes are tightly coupled to the internal implementation choices of their parent. If you innocently tweak how a base class method calculates or stores data, dozens of downstream subclasses across the organization can silently malfunction without warning!
Danger: Deep Inheritance Trees
Entity ➔ Person ➔ Employee ➔ Manager ➔ RegionalManager ➔ SeniorRegionalManager

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:

class AmphibiousVehicle(Car, Boat):
    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__.

Architectural Advice: Multiple inheritance can spawn dreaded diamond dependencies. In professional codebases, reserve multiple inheritance almost exclusively for lightweight, stateless Mixin classes.

09 When to Use vs. When to Avoid Inheritance

When to Use (Green Flags)
  • • 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).
When to Avoid (Red Flags)
  • • 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?

Key Takeaways

The "is-a" Test: Use inheritance only when the child is conceptually a true specialization of the parent.
Method Overriding: Child classes can substitute generic parent behavior with customized implementations.
Power of super(): The standard idiom for chaining constructors and extending base methods without duplication.
Architectural Caution: Avoid deep inheritance trees and the fragile base class dilemma; consider composition first.