Python Mastery: Complete Beginner to Professional
HomeInsightsCoursesPythonInheritance & MRO

Inheritance: The DNA of Software

From Royal Families to RPG Character Systems: Mastering code reuse and polymorphism.

1. The Big Idea (ELI5)

👶 Explain Like I'm 10: The Royal Family

Imagine a Royal Family.

  • The King (Parent Class): He has "Blue Eyes" and "A Huge Castle".
  • The Prince (Child Class): When the Prince is born, he automatically gets "Blue Eyes" and "A Huge Castle". He didn't have to work for it; he Inherited it.
  • The Difference: But the Prince wants to be cool. He buys a "Ferrari" (New Attribute). The King doesn't have a Ferrari, but the Prince does.
  • Overriding: The King walks very slowly. The Prince walks very fast. The Prince has Overridden the walking behavior.

In coding, this saves us from rewriting "Blue Eyes" and "Huge Castle" for every new child. We write it once in the Parent, and everyone gets it for free!

2. The Philosophy: DRY and Hierarchy

One of the core principles of software engineering is DRY (Don't Repeat Yourself). If you are building a game with `Dog`, `Cat`, and `Cow`, you don't want to copy-paste the `eat()` and `sleep()` functions three times.

Inheritance allows you to create a parent class (e.g., `Animal`) that holds the shared logic, and child classes that inherit that logic automatically.

PYTHON
# The BAD Way: Copy-Pasting
class Dog:
    def eat(self): print("Eating...")
    def bark(self): print("Woof")

class Cat:
    def eat(self): print("Eating...") # <--- Repeated Code!
    def meow(self): print("Meow")

# The GOOD Way: Inheritance
class Animal:
    def eat(self): print("Eating...")

class Dog(Animal): # Dog "IS A" Animal
    def bark(self): print("Woof")

class Cat(Animal): # Cat "IS A" Animal
    def meow(self): print("Meow")

3. Basic Inheritance Mechanism

Let's create the classic Animal hierarchy. Notice how `Dog` and `Cat` are empty, but they can still do things!

PYTHON
class Animal:
    def __init__(self, name):
        self.name = name

    def sleep(self):
        print(f"{self.name} is sleeping... Zzz.")

    def speak(self):
        # This is a "Abstract Method" in spirit. 
        # The generic Animal doesn't know what sound to make.
        print("??? (Generic Animal Sound)")

# Syntax: class Child(Parent):
class Dog(Animal):
    def speak(self):
        print("WOOF! WOOF!")

# Cat inherits EVERYTHING and changes nothing
class Cat(Animal):
    pass 

d = Dog("Buddy")
c = Cat("Whiskers")

d.sleep()  # Inherited from Animal -&gt; "Buddy is sleeping... Zzz."
d.speak()  # Overridden in Dog -&gt; "WOOF! WOOF!"

c.sleep()  # Inherited -&gt; "Whiskers is sleeping... Zzz."
c.speak()  # Fallback to Animal -&gt; "??? (Generic Animal Sound)"

4. The `super()` Function

Sometimes you don't want to replace the parent's logic; you want to extend it. You want to run the parent's code AND your own code. This is where `super()` comes in.

PYTHON
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

class Manager(Employee):
    def __init__(self, name, salary, team_size):
        # BAD WAY: self.name = name; self.salary = salary (Don't repeat logic!)
        
        # GOOD WAY: Call the parent's constructor
        super().__init__(name, salary)
        
        # Then add the new stuff
        self.team_size = team_size

m = Manager("Alice", 90000, 5)
print(m.name)      # Alice (Set by Employee default logic)
print(m.team_size) # 5 (Set by Manager logic)

Analogy: Think of `super()` like calling your manager for help. "Hey boss, do the standard paperwork for this new employee, and I'll handle the special team assignment."

5. Real World Case Study: RPG Character System

Let's utilize everything we learned (Inheritance, Overriding, `super()`) to build a scalable character system for a text-based RPG game. This pattern is used in real game engines like Unity or Unreal (conceptually).

PYTHON
class Character:
    def __init__(self, name, health):
        self.name = name
        self.health = health

    def attack(self, target):
        """Base attack logic (e.g. punching)"""
        print(f"{self.name} punches {target.name} for 5 damage!")
        target.take_damage(5)

    def take_damage(self, amount):
        self.health -= amount
        print(f"{self.name} has {self.health} HP left.")

class Warrior(Character):
    def __init__(self, name, health, armor):
        # Initialize generic stats
        super().__init__(name, health) 
        # Initialize specific stat
        self.armor = armor 

    # Override: Specialized Damage Logic
    def take_damage(self, amount):
        # Warriors reduce damage by their armor value
        reduced_amount = max(0, amount - self.armor)
        print(f"{self.name}'s armor blocked {amount - reduced_amount} damage.")
        super().take_damage(reduced_amount)

class Mage(Character):
    def __init__(self, name, health, mana):
        super().__init__(name, health)
        self.mana = mana

    # Override: Specialized Attack Logic
    def attack(self, target):
        if self.mana >= 10:
            self.mana -= 10
            print(f"{self.name} casts Fireball at {target.name} for 20 damage!")
            target.take_damage(20)
        else:
            print(f"{self.name} is out of mana!")
            super().attack(target) # Fallback to punching

# The Battle Arena
arthur = Warrior("Arthur", 100, 3) # 3 Armor
merlin = Mage("Merlin", 60, 20)    # 20 Mana

# Mage attacks Warrior
merlin.attack(arthur) 
# Output:
# Merlin casts Fireball at Arthur for 20 damage!
# Arthur's armor blocked 3 damage.
# Arthur has 83 HP left.

# Warrior attacks Mage
arthur.attack(merlin)
# Output:
# Arthur punches Merlin for 5 damage!
# Merlin has 55 HP left.

6. Multiple Inheritance & The Diamond Problem

Unlike Java, Python supports Multiple Inheritance. A child can have multiple parents. This sounds cool ("I want the strength of a Bear and the speed of a Cheetah!"), but it introduces chaos.

PYTHON
class Mother:
    def eyes(self): return "Brown"

class Father:
    def eyes(self): return "Blue"

class Child(Mother, Father):
    pass

c = Child()
print(c.eyes()) # Brown? Blue? Purple?

Method Resolution Order (MRO)

Python uses the C3 Linearization Algorithm to decide who wins. It looks at the order you listed them: `(Mother, Father)`. Mother comes first, so she wins.

PYTHON
print(Child.mro())
# Output: [<class 'Child'>, <class 'Mother'>, <class 'Father'>, <class 'object'>]

The "Diamond Problem" occurs when A is parent of B and C, and D inherits from B and C. If B and C both override a method from A, which one does D get? Python's MRO solves this deterministically (Depth-first, then Left-to-Right).

7. Composition Over Inheritance

The Golden Rule of OOP Design

New programmers LOVE inheritance. They overuse it. Don't say: "A Car IS A Engine". That's weird. Say: "A Car HAS AN Engine". This is called Composition.

PYTHON
# The WRONG Way (Inheritance)
class Engine:
    def start_engine(self): print("Vrooom")

class Car(Engine): # A Car IS NOT an Engine!
    pass

# The RIGHT Way (Composition)
class Car:
    def __init__(self):
        self.engine = Engine() # A Car HAS An Engine
    
    def start(self):
        self.engine.start_engine() # Delegation

8. Liskov Substitution Principle (LSP)

This is the "L" in SOLID. It states:"You should be able to replace a Parent object with a Child object without breaking the code."

Violation Example: If `Bird` has a `.fly()` method, and you create `Penguin(Bird)` but make `.fly()` raise an Error, you violated LSP. Because now, any code expecting a `Bird` to fly will crash when given a `Penguin`.

9. Interview FAQ

Q1: What is MRO?

Method Resolution Order. It's the order in which Python searches for a method in a hierarchy of classes. It follows the C3 Linearization algorithm.

Q2: Why is multiple inheritance considered dangerous?

It leads to the "Diamond Problem" where two parents define the same method. It makes code hard to trace. Mixins are a safer alternative.

Q3: What does `super()` actually do?

It returns a temporary object (proxy) of the superclass that allows you to call its methods, respecting MRO.

Q4: Composition vs Inheritance?

Use Inheritance for "Is-A" (Dog is Animal). Use Composition for "Has-A" (Car has Engine). Composition is generally more flexible.

10. Extended Technical Reference

Inheritance Patterns

Different languages handle inheritance differently.

FeaturePythonJava/C#JavaScript
Multiple Inheritance✅ Yes (via MRO)❌ No (Single Parent only)❌ No (Prototypal Chain)
InterfacesSimulated via `ABCs`Built-in keyword `interface`None
Access to Parent`super()``super` or `base``super()`

Historical Context: The MRO Evolution

In ancient Python (pre-2.3), the Method Resolution Order was simple Depth-First Search (DFS). This caused severe bugs in the "Diamond Problem" where the "root" class would be visited twice or weirdly shadowed.

Python 2.3 introduced the C3 Linearization Algorithm (originally from the Dylan language). This complex algorithm ensures that every class in the hierarchy is visited exactly once and in a predictable order. If you create a hierarchy that makes a consistent order impossible (a paradox), Python raises a `TypeError`.

What's Next?

We have the Blueprint (Classes) and the DNA (Inheritance). Now let's explore the **4 Pillars of implementation** in detail: Encapsulation, Abstraction, Inheritance (Recap), and Polymorphism.