Inheritance & MRO

intermediate inheritance super MRO diamond-problem C3-linearization

Inheritance lets one class reuse the code of another. The child class (subclass) inherits all methods and attributes from the parent class (superclass) and can override or extend them.

Single Inheritance

The simplest form. We put the parent class in parentheses.

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

    def speak(self):
        return f"{self.name} makes a sound"

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

d = Dog("Buddy")
print(d.speak())  # Buddy says Woof!
print(d.name)     # Buddy — inherited from Animal

Why super()?

super() gives us a reference to the parent class. We use it to call the parent’s methods without hardcoding the parent class name.

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # call Animal.__init__
        self.breed = breed      # add new attribute

d = Dog("Buddy", "Labrador")
print(d.name, d.breed)  # Buddy Labrador

Without super(), we’d have to write Animal.__init__(self, name) — which breaks if we rename the parent or change the hierarchy.

Multiple Inheritance

Python supports inheriting from more than one class. This is where things get interesting.

class Flyer:
    def fly(self):
        return "I can fly!"

class Swimmer:
    def swim(self):
        return "I can swim!"

class Duck(Flyer, Swimmer):  # inherits from both
    pass

d = Duck()
print(d.fly())   # I can fly!
print(d.swim())  # I can swim!

The Diamond Problem

The diamond problem happens when a class inherits from two classes that share a common ancestor. Which version of the shared method should we get?

The Diamond Problem
A (object)
def greet(self)
B(A)
def greet(self)
C(A)
def greet(self)
D(B, C)
Which greet() runs?
MRO for D: D → B → C → A → object

C3 Linearization (MRO)

Python solves the diamond problem using an algorithm called C3 linearization. It produces the Method Resolution Order (MRO) — a flat list that determines the exact order Python searches for methods.

We can inspect it with .mro() or __mro__:

class A:
    def greet(self): return "A"

class B(A):
    def greet(self): return "B"

class C(A):
    def greet(self): return "C"

class D(B, C):
    pass

print(D.mro())  # [D, B, C, A, object]
d = D()
print(d.greet())  # "B" — B comes first in the MRO

The key rules of C3 linearization:

  1. Children always come before parents.
  2. The order of parents in class D(B, C) is preserved (B before C).
  3. Each class appears only once.

How super() Works with MRO

Here’s the part that trips people up. super() doesn’t just call the parent class — it calls the next class in the MRO. This is crucial for cooperative multiple inheritance.

class A:
    def greet(self):
        print("A")

class B(A):
    def greet(self):
        print("B")
        super().greet()  # next in MRO, not necessarily A

class C(A):
    def greet(self):
        print("C")
        super().greet()  # next in MRO

class D(B, C):
    def greet(self):
        print("D")
        super().greet()  # next in MRO

D().greet()  # D → B → C → A (follows MRO exactly)

Cooperative Multiple Inheritance

For multiple inheritance to work cleanly, every class in the hierarchy should call super(). This is called “cooperative” because each class cooperates by passing control along the MRO chain.

class Base:
    def __init__(self, **kwargs):
        pass  # end of the chain — absorb leftover kwargs

class Named(Base):
    def __init__(self, name, **kwargs):
        super().__init__(**kwargs)
        self.name = name

class Aged(Base):
    def __init__(self, age, **kwargs):
        super().__init__(**kwargs)
        self.age = age

class Person(Named, Aged):
    pass

p = Person(name="Manish", age=25)
print(p.name, p.age)  # Manish 25

isinstance() and issubclass()

print(isinstance(d, D))  # True
print(isinstance(d, A))  # True — D inherits from A
print(issubclass(D, A))  # True — D is a subclass of A
print(issubclass(B, C))  # False — B and C are siblings

In simple language, inheritance lets us reuse code by creating parent-child relationships between classes. When multiple inheritance creates ambiguity, Python uses C3 linearization to produce a clear method resolution order. And super() always follows that MRO, not just “the parent.”