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?
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:
- Children always come before parents.
- The order of parents in
class D(B, C)is preserved (B before C). - 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.”