🐍 Python Course · Stage 4 · Lesson 68 of 89 · Concept: Inheritance
Course Overview →

🟩 These concept pages connect Python to programming in general. Read Beginner tab first.

← Concept: Objects Concept: Error Handling →
thecodex.expert · The Codex Family of Knowledge
CS Concepts

Inheritance

How a class builds on another — acquiring all its capabilities and changing only what is different.

CS Concepts Language-independent ~18 min read Last verified:
Canonical Definition

Inheritance is a mechanism by which a class (subclass) acquires the attributes and methods of another class (superclass), enabling code reuse through is-a relationships and allowing method overriding to specialise inherited behaviour.

🟩 Beginner

Building on existing classes

What you'll learn: The is-a relationship that justifies inheritance. How a subclass inherits attributes and methods from a parent. The super() function. When NOT to use inheritance.

How to read this tab: Read alongside Object-Oriented Python which covers Python-specific syntax in detail.

⏱ 20 min 📄 3 sections 🔶 Prerequisite: Classes
One sentence that captures it

Inheritance lets a new class start with everything an existing class already has, then add or change only what is different — write shared behaviour once, reuse it everywhere.

Inheritance is only appropriate for genuine is-a relationships. Dog IS-A Animal: ✓ inherit. Car HAS-A Engine: ✗ don't inherit, compose. The test: would it make sense to use a Dog everywhere an Animal is expected? Yes → inherit. The most common misuse of inheritance is using it for code reuse when composition would be cleaner.

The is-a relationship

A SavingsAccount is a BankAccount. A Dog is an Animal. The child class (subclass) inherits all attributes and methods of the parent class (superclass) and can add new ones or override existing ones.

Pythoninheritance.py
class Animal:
    def __init__(self, name): self.name = name
    def speak(self): return f"{self.name} makes a sound"

class Dog(Animal):
    def speak(self):     # override
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

rex = Dog("Rex")
whiskers = Cat("Whiskers")
print(rex.speak())     # Rex says Woof!
print(whiskers.speak()) # Whiskers says Meow!
print(isinstance(rex, Animal))  # True
💡

Always use super() — never call the parent directly by name. Instead of Animal.__init__(self) write super().__init__(). Why: super() respects the MRO — in multiple inheritance, it ensures each parent is called exactly once in the right order. Calling by name breaks this in complex hierarchies.

super(): calling the parent

super() gives access to the parent's methods without naming it explicitly. Essential in __init__: the subclass must call the parent's init to ensure parent attributes are set up.

Pythonsuper.py
class SavingsAccount(BankAccount):
    def __init__(self, owner, balance=0, rate=0.04):
        super().__init__(owner, balance)  # call parent __init__
        self.rate = rate

    def add_interest(self):
        interest = self.balance * self.rate
        self.deposit(interest)   # inherited method
        return interest

Prefer composition over inheritance. Inheritance creates tight coupling between classes — a change in the parent can break all subclasses. Composition (storing an object as an attribute) is more flexible. The rule: if you need the subclass to be usable as the parent type, use inheritance. If you just want to reuse the parent's code, use composition.

When NOT to use inheritance

Use inheritance only for genuine is-a relationships. For code reuse without an is-a relationship, composition is better: give a class an instance of another class (has-a). The Gang of Four (Gamma et al., 1994) advise: favour composition over inheritance.

✅ Beginner tab complete — check your understanding

  • I can explain inheritance using an is-a relationship (Dog IS-A Animal)
  • I can write a subclass that inherits from a parent and adds new methods
  • I know when NOT to use inheritance: when composition (has-a) is cleaner
  • I can use super() to call the parent class's method from the subclass

Continue to Error Handling →

🔵 Intermediate

Multiple inheritance, MRO, and abstract classes

What you'll learn: Multiple inheritance — inheriting from more than one class. The C3 Method Resolution Order that determines which parent's method is called. Abstract base classes for defining interfaces.

How to read this tab: The MRO section is directly relevant to real Python code. Call type.mro() on any class to see the order — try it on list, dict, and your own classes.

⏱ 20 min 📄 2 sections 🔶 Prerequisite: Beginner tab
📎

Python's C3 linearisation resolves multiple inheritance correctly. If class C inherits from both A and B, and both have a method foo(), Python uses the MRO to decide which foo() to call. The order is computed by C3 linearisation — a deterministic algorithm that respects both the local order of bases and the global inheritance hierarchy.

Multiple inheritance and the MRO

Python supports class C(A, B). Python's C3 linearisation resolves method lookup consistently. C.__mro__ shows the lookup order. Java and Kotlin disallow multiple class inheritance (diamond problem prevention) but allow multiple interface implementation.

Pythonmro.py
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

print(D.__mro__)
# D, B, C, A, object
print(D().hello())  # "B" — MRO says check D, B, C, A in order
💡

Abstract classes define an interface without an implementation. Use from abc import ABC, abstractmethod. Mark methods with @abstractmethod to force subclasses to implement them. Instantiating an abstract class raises TypeError. This is how Python enforces that a subclass provides required methods — closest Python gets to Java's interface.

Abstract classes

An abstract class cannot be instantiated — it exists to be subclassed. Abstract methods must be implemented by concrete subclasses. Python: from abc import ABC, abstractmethod. Java: abstract class. The compiler/runtime enforces that all abstract methods are implemented.

Pythonabstract.py
from abc import ABC, abstractmethod
class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

class Circle(Shape):
    def __init__(self, r): self.r = r
    def area(self): return 3.14159 * self.r ** 2

# Shape() raises TypeError — can't instantiate abstract
c = Circle(5)
print(c.area())  # 78.54
📎

The formal rule for correct inheritance. A subclass must be substitutable for its parent without breaking the program. If your Dog class inherits from Animal but breaks when used where Animal is expected (wrong return type, narrower accepted inputs, unexpected exceptions), it violates LSP. LSP violations indicate the inheritance relationship is wrong — usually composition would be better.

Liskov Substitution Principle

LSP (Liskov & Wing, 1994): a subclass must be usable wherever its parent is expected without breaking the program. Classic violation: Square overriding Rectangle's set_width/set_height breaks code that sets width and height independently.

Commonly confused
Inheritance is not the primary tool for code reuse. It creates tight coupling. Composition — giving a class an instance of another class — is looser and more flexible.
super() in Python follows the MRO, not the direct parent. In multiple inheritance, super() calls the next class in the MRO, which may not be what you expect.
Always call super().__init__() when overriding __init__. If you don't, the parent's initialisation does not run — attributes set by the parent will be missing, causing AttributeError.
How this connects
Requires first
Enables
Confused with

✅ Intermediate tab complete — check your understanding

  • I can use super() correctly in a multiple inheritance hierarchy
  • I can call type.mro() or ClassName.__mro__ to see the resolution order
  • I know what an abstract class is and how to define one with ABC and @abstractmethod

Continue to Error Handling →

🔴 Expert

LSP, the diamond problem, covariance and contravariance

What you'll learn: The Liskov Substitution Principle — the formal rule for when inheritance is correct. The diamond problem and how C3 linearisation solves it. Covariance and contravariance in type systems.

How to read this tab: Read when studying software design principles or type theory.

⏱ 20 min 📄 3 sections 🔶 Prerequisite: After Stage 2

The diamond problem and C3 linearisation

With class D(B, C) where both B and C inherit from A: which path to A's method does D use? C3 linearisation (Barrett et al., 1996) ensures A's method is called exactly once and produces a total ordering: L(D) = D + merge(L(B), L(C), [B, C]). Merge repeatedly takes the head of the first list if it does not appear in the tail of any other list.

Covariance and contravariance

Type-safe overriding requires careful treatment. Covariant return type: an override can return a more specific type (safe — callers expecting T get S⊂T). Contravariant parameter type: an override can accept a less specific type (safe — handles more cases). Java supports covariant return types; parameter types are invariant. Java array types are covariant — a historical mistake that enables ArrayStoreException at runtime.

Specification reference

Liskov, B. & Wing, J. (1994). ACM TOPLAS 16(6). Barrett, D. et al. (1996). OOPSLA '96. Python abc module: docs.python.org/3/library/abc.html. Java Language Specification §8.4.8 — Overriding.

✅ Expert tab complete

  • I can explain the Liskov Substitution Principle (a subclass must be usable wherever the parent is expected)
  • I can explain the diamond problem and how MRO resolves it

Continue to Error Handling →

Sources

1
Liskov, B. & Wing, J. (1994). "A behavioral notion of subtyping." ACM TOPLAS 16(6), 1811–1841.
2
Barrett, D. et al. (1996). "A Monotonic Superclass Linearisation for Dylan." OOPSLA '96. ACM.
3
Python Software Foundation. abc module. docs.python.org/3/library/abc.html.
4
Java Language Specification §8.4.8 — Overriding. Oracle. docs.oracle.com/javase/specs/.
Source confidence: High Last verified: Primary source: Liskov, B. & Wing, J. (1994). ACM TOPLAS 16(6)