Python Inheritance: Subclasses, Method Resolution, and Multiple Inheritance
Inheritance is the mechanism by which one class gains the attributes and methods of another. It is one of OOP’s most powerful tools for avoiding code duplication — and one of the most frequently misused. This guide covers how it works in Python, what the method resolution order (MRO) actually does, and when you should consider composition instead.
The Basic Mechanic
A class that inherits from another is called a subclass or child class. The class it inherits from is the superclass or parent class. The subclass automatically has access to all the parent’s methods and class-level attributes.
class Vehicle: def __init__(self, make, year): self.make = make self.year = year
def start(self): return f"{self.make} engine starting..."
def describe(self): return f"{self.year} {self.make}"
class Car(Vehicle): def __init__(self, make, year, doors): super().__init__(make, year) # call parent's __init__ self.doors = doors
def describe(self): # override parent method base = super().describe() return f"{base} ({self.doors}-door)"
class Truck(Vehicle): def __init__(self, make, year, payload_tonnes): super().__init__(make, year) self.payload_tonnes = payload_tonnes
car = Car("Toyota", 2022, 4)truck = Truck("Volvo", 2021, 25)
print(car.start()) # Toyota engine starting...print(car.describe()) # 2022 Toyota (4-door)print(truck.describe()) # 2021 Volvoprint(truck.start()) # Volvo engine starting...Car and Truck both inherit start() from Vehicle. Car overrides describe() to add door count. Truck uses Vehicle’s describe() unchanged.
Method Resolution Order (MRO)
When you call a method on an object, Python follows a specific search order to find it. For single inheritance, this is straightforward: look at the instance, then the class, then the parent, all the way up to object (the base of every Python class).
With multiple inheritance, the order is determined by the C3 linearisation algorithm. You can inspect it with __mro__ or mro():
class A: def who(self): return "A"
class B(A): def who(self): return "B"
class C(A): def who(self): return "C"
class D(B, C): pass
d = D()print(d.who()) # B — first in MRO after Dprint(D.__mro__)# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)The MRO for D is D → B → C → A → object. Python searches left to right, depth-first, with a constraint that no class appears before its subclasses. If you call super().who() inside B, Python continues the MRO search from after B — which means it finds C.who(), not A.who(). This is cooperative multiple inheritance.
Multiple Inheritance
Python supports inheriting from more than one class at once.
class Flyable: def fly(self): return "Taking off..."
class Swimmable: def swim(self): return "Diving in..."
class Duck(Flyable, Swimmable): def __init__(self, name): self.name = name
def describe(self): return f"{self.name} can fly and swim"
donald = Duck("Donald")print(donald.fly()) # Taking off...print(donald.swim()) # Diving in...print(donald.describe()) # Donald can fly and swimThe duck gets all methods from both mixins. This works cleanly when the parent classes are designed not to conflict — they have different method names and each calls super() correctly.
Cooperative super() in Multiple Inheritance
When multiple classes form a chain and all use super() correctly, Python’s MRO ensures every class in the chain runs its method exactly once.
class Base: def setup(self): print("Base.setup")
class LogMixin: def setup(self): print("LogMixin.setup") super().setup()
class CacheMixin: def setup(self): print("CacheMixin.setup") super().setup()
class Service(LogMixin, CacheMixin, Base): def setup(self): print("Service.setup") super().setup()
s = Service()s.setup()# Service.setup# LogMixin.setup# CacheMixin.setup# Base.setupEach class calls super().setup(), handing control to the next class in the MRO. Remove super().setup() from any one class, and the chain breaks there.
isinstance() and issubclass()
print(isinstance(car, Car)) # Trueprint(isinstance(car, Vehicle)) # True — Car is a subclass of Vehicleprint(isinstance(car, Truck)) # False
print(issubclass(Car, Vehicle)) # Trueprint(issubclass(Truck, Car)) # Falseisinstance checks if an object is an instance of a class or any of its ancestors. This is preferable to checking type(obj) == SomeClass, which fails for subclasses.
Composition vs Inheritance: When to Choose Each
Inheritance expresses an is-a relationship: a Car is a Vehicle. Composition expresses a has-a relationship: a Car has an Engine.
# Inheritance — Car IS-A Vehicleclass Car(Vehicle): pass
# Composition — Car HAS-A Engineclass Engine: def __init__(self, horsepower): self.horsepower = horsepower
def start(self): return f"{self.horsepower}hp engine running"
class Car: def __init__(self, make, year, horsepower): self.make = make self.year = year self._engine = Engine(horsepower) # composed object
def start(self): return self._engine.start()
car = Car("Ford", 2023, 180)print(car.start()) # 180hp engine runningPrefer composition when:
- The relationship is “has-a” rather than “is-a”.
- You want to swap out behaviour at runtime (replace
Enginewith anElectricMotor). - You are reusing a small piece of behaviour from a class that you do not want to inherit everything from.
Prefer inheritance when:
- The relationship is genuinely hierarchical.
- The subclass should be usable anywhere the parent is expected.
- You are extending, not replacing, the parent’s behaviour.
Common Pitfalls
Forgetting super().__init__(). If the parent’s __init__() sets up required attributes, omitting super() leaves the object in an invalid state.
Deep inheritance chains. Classes that inherit four or five levels deep become very hard to reason about. Flatten hierarchies by extracting mixins or using composition.
Inheriting just to reuse one method. If you only need one function from a class, import it as a module function or use composition. Inheritance creates a permanent dependency.
Diamond inheritance without cooperative super(). If any class in a multiple-inheritance diamond fails to call super(), some classes in the chain will not run at all.