Python Inheritance
Learn Python inheritance: subclasses, method overriding, super(), multilevel and multiple inheritance, MRO, and isinstance checks — with runnable examples.
Understanding Python Inheritance
Inheritance lets you build a new class on top of an existing one. The new class (the subclass or child class) automatically gets all the attributes and methods of the existing class (the superclass, base class, or parent class). You can then add extra behaviour or selectively override what you inherited.
This chapter covers:
- The syntax for creating subclasses
- How
super()works and why you should use it - Method overriding and calling the parent's version
- Single, multilevel, and multiple inheritance
- Python's Method Resolution Order (MRO)
isinstance()andissubclass()for runtime type checks- Common gotchas
Before reading this chapter, make sure you are comfortable with Python classes and objects. For related OOP pillars, see Python Encapsulation and Python Abstract Classes.
The Syntax for Inheritance
To create a subclass, write the parent class name inside parentheses after the new class name:
class ParentClass:
pass
class ChildClass(ParentClass):
passA minimal but concrete example with a Vehicle base class and a Car subclass:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
print(f"{self.make} {self.model} started.")
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year) # delegate to Vehicle.__init__
self.num_doors = num_doors
camry = Car("Toyota", "Camry", 2023)
camry.start() # Toyota Camry started.
camry.stop() # Toyota Camry stopped.
print(camry.num_doors) # 4Car inherits start() and stop() from Vehicle without duplicating them. Adding num_doors is the only new concern Car has to handle.
Using super()
super() returns a proxy object that delegates method calls to the parent class. Its most common use is inside __init__ to initialise the parent's attributes before adding the child's own:
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year) # runs Vehicle.__init__
self.num_doors = num_doorsWhy prefer super() over writing Vehicle.__init__(self, ...) directly?
- Maintenance: if you rename or replace the parent class, you only change one line.
- Multiple inheritance:
super()follows Python's Method Resolution Order (MRO), so every class in the chain gets called correctly. Hard-coding the parent name skips that logic.
You can call super() for any method, not just __init__:
class Car(Vehicle):
def start(self):
super().start() # call Vehicle.start first
print(f"({self.num_doors}-door model ready)")Method Overriding
A subclass overrides a parent method by defining a method with the same name. Python always calls the most-derived version first:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def start(self):
print(f"{self.make} {self.model} started.")
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def start(self):
print(f"{self.make} {self.model} revved the engine and started.")
camry = Car("Toyota", "Camry", 2023)
camry.start() # Toyota Camry revved the engine and started.
camry.stop() # Toyota Camry stopped. (inherited, not overridden)When you want to extend rather than replace the parent behaviour, call super() inside the override:
class Car(Vehicle):
def start(self):
super().start() # Vehicle.start runs first
print("Seatbelt reminder: buckle up!") # then add the extra stepInheriting Class Attributes
Inheritance applies to class attributes (shared across instances) as well. A subclass can override them the same way it overrides methods:
class Vehicle:
wheels = 4
class Motorcycle(Vehicle):
wheels = 2 # override the class attribute
class Car(Vehicle):
pass # inherits wheels = 4
print(Motorcycle.wheels) # 2
print(Car.wheels) # 4
print(Vehicle.wheels) # 4Types of Inheritance
Single Inheritance
One child, one parent — the most common pattern.
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):
return f"{self.name} says woof!"
dog = Dog("Rex")
print(dog.speak()) # Rex says woof!Multilevel Inheritance
A subclass can itself be subclassed, creating a chain:
class Vehicle:
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
def stop(self):
print(f"{self.make} {self.model} stopped.")
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
super().__init__(make, model, year)
self.num_doors = num_doors
def start(self):
print(f"{self.make} {self.model} started.")
class ElectricCar(Car):
def __init__(self, make, model, year, range_km):
super().__init__(make, model, year) # calls Car.__init__
self.range_km = range_km
def start(self):
print(f"{self.make} {self.model} powered up silently.")
def charge(self):
print(f"Charging... range is {self.range_km} km.")
tesla = ElectricCar("Tesla", "Model 3", 2024, 580)
tesla.start() # Tesla Model 3 powered up silently.
tesla.charge() # Charging... range is 580 km.
tesla.stop() # Tesla Model 3 stopped. (inherited from Vehicle)ElectricCar sits two levels below Vehicle. Each call to super().__init__ moves one level up the chain, so all three __init__ methods run.
Multiple Inheritance
Python lets a class inherit from more than one parent. List them in the parentheses, separated by commas:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound."
class Swimmer:
def swim(self):
return f"{self.name} swims."
class Flyer:
def fly(self):
return f"{self.name} flies."
class Duck(Animal, Swimmer, Flyer):
def speak(self):
return f"{self.name} says quack!"
donald = Duck("Donald")
print(donald.speak()) # Donald says quack!
print(donald.swim()) # Donald swims.
print(donald.fly()) # Donald flies.Multiple inheritance is powerful but introduces complexity. Use mixins — small, single-purpose classes that add one capability — to keep it manageable. Swimmer and Flyer above are exactly that pattern.
The Method Resolution Order (MRO)
When Python looks up a method or attribute, it follows a deterministic search order called the Method Resolution Order (MRO). For single inheritance the order is obvious (child → parent → grandparent → object). For multiple inheritance Python uses the C3 linearisation algorithm to produce an unambiguous order.
Inspect the MRO of any class with .__mro__ or help():
print(Duck.__mro__)
# (<class 'Duck'>, <class 'Animal'>, <class 'Swimmer'>, <class 'Flyer'>, <class 'object'>)The MRO matters most when multiple parents define the same method. Python picks the first class in the MRO that defines it. This is why super() is important in multiple-inheritance hierarchies: each class in the MRO calls super(), so every class in the chain gets a chance to run.
class Base:
def greet(self):
print("Hello from Base")
class Left(Base):
def greet(self):
print("Hello from Left")
super().greet()
class Right(Base):
def greet(self):
print("Hello from Right")
super().greet()
class Child(Left, Right):
def greet(self):
print("Hello from Child")
super().greet()
Child().greet()
# Hello from Child
# Hello from Left
# Hello from Right
# Hello from BaseEach call to super() follows the MRO (Child → Left → Right → Base), so every greet() runs exactly once despite Base appearing as a parent of both Left and Right. This is the diamond problem — Python's MRO solves it cleanly.
Checking Inheritance at Runtime
isinstance(obj, cls)
Returns True if obj is an instance of cls or any of its subclasses:
tesla = ElectricCar("Tesla", "Model 3", 2024, 580)
print(isinstance(tesla, ElectricCar)) # True
print(isinstance(tesla, Car)) # True — Car is a parent
print(isinstance(tesla, Vehicle)) # True — Vehicle is a grandparent
print(isinstance(tesla, str)) # FalseThis is more reliable than comparing type(obj) == Car, which returns False for subclasses.
issubclass(sub, cls)
Returns True if sub is a subclass of cls (including itself):
print(issubclass(ElectricCar, Vehicle)) # True
print(issubclass(Car, ElectricCar)) # False
print(issubclass(Car, Car)) # True — a class is a subclass of itselfCommon Gotchas
Forgetting to call super().__init__()
If the child's __init__ does not call super().__init__(), the parent's attributes are never set. Any method that expects them will raise AttributeError:
class Car(Vehicle):
def __init__(self, make, model, year, num_doors=4):
# forgot super().__init__(...)
self.num_doors = num_doors
c = Car("Toyota", "Camry", 2023)
c.start() # AttributeError: 'Car' object has no attribute 'make'Overriding without calling super() when you meant to extend
If you override a method and forget super(), the parent's version never runs. This silently drops behaviour that other code may depend on.
Deep multiple-inheritance hierarchies
More than two levels of multiple inheritance is very hard to reason about. If you find yourself writing class Foo(A, B, C, D), consider using composition — storing instances as attributes — instead.
Benefits of Inheritance
- Code reuse — shared logic lives in one place; subclasses inherit it for free.
- Extensibility — you can add or change behaviour in a subclass without touching the parent, keeping existing callers unchanged.
- Polymorphism — functions that accept a
Vehiclework equally well with anyCar,Motorcycle, orElectricCar. See Python Polymorphism for the full picture.