Inheritance

Learn about inheritance in Python, including single, multiple, multilevel, and hierarchical inheritance, method overriding, Method Resolution Order (MRO), and the super() function.

Ali Berro

By Ali Berro

10 min read Section 2
From: Python Fundamentals: From Zero to Hero

Inheritance

Inheritance is a fundamental OOP concept that allows a class (child/derived class) to inherit attributes and methods from another class (parent/base class). This promotes code reusability and establishes a relationship between classes.

Basic Inheritance

In Python, inheritance is achieved by passing the parent class as an argument to the child class definition:

basic-inheritance.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):
return f"{self.name} barks"
class Cat(Animal):
def speak(self):
return f"{self.name} meows"
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # Buddy barks
print(cat.speak()) # Whiskers meows

Types of Inheritance

Single Inheritance

A child class inherits from a single parent class:

single-inheritance.py
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def start(self):
return f"{self.brand} {self.model} started"
class Car(Vehicle):
def __init__(self, brand, model, doors):
super().__init__(brand, model)
self.doors = doors
def honk(self):
return "Beep beep!"
car = Car("Toyota", "Camry", 4)
print(car.start()) # Toyota Camry started
print(car.honk()) # Beep beep!

Multiple Inheritance

A child class inherits from multiple parent classes:

multiple-inheritance.py
class Flyable:
def fly(self):
return "Flying high!"
class Swimmable:
def swim(self):
return "Swimming deep!"
class Duck(Flyable, Swimmable):
def __init__(self, name):
self.name = name
def quack(self):
return f"{self.name} says quack!"
duck = Duck("Donald")
print(duck.fly()) # Flying high!
print(duck.swim()) # Swimming deep!
print(duck.quack()) # Donald says quack!

Multilevel Inheritance

A class inherits from a child class, creating a chain:

multilevel-inheritance.py
class Animal:
def __init__(self, name):
self.name = name
class Mammal(Animal):
def give_birth(self):
return f"{self.name} gives birth to live young"
class Dog(Mammal):
def bark(self):
return f"{self.name} barks"
dog = Dog("Buddy")
print(dog.name) # Buddy (from Animal)
print(dog.give_birth()) # Buddy gives birth to live young (from Mammal)
print(dog.bark()) # Buddy barks (from Dog)

Hierarchical Inheritance

Multiple child classes inherit from a single parent class:

hierarchical-inheritance.py
class Shape:
def __init__(self, name):
self.name = name
def area(self):
return 0
class Rectangle(Shape):
def __init__(self, name, length, width):
super().__init__(name)
self.length = length
self.width = width
def area(self):
return self.length * self.width
class Circle(Shape):
def __init__(self, name, radius):
super().__init__(name)
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
rect = Rectangle("Rectangle", 5, 3)
circle = Circle("Circle", 4)
print(f"{rect.name} area: {rect.area()}") # Rectangle area: 15
print(f"{circle.name} area: {circle.area()}") # Circle area: 50.26544

Method Overriding

Method overriding occurs when a child class provides its own implementation of a method that exists in the parent class:

method-overriding.py
class Animal:
def make_sound(self):
return "Some generic sound"
class Dog(Animal):
def make_sound(self):
return "Woof!"
class Cat(Animal):
def make_sound(self):
return "Meow!"
animal = Animal()
dog = Dog()
cat = Cat()
print(animal.make_sound()) # Some generic sound
print(dog.make_sound()) # Woof!
print(cat.make_sound()) # Meow!

The super() Function

The super() function allows you to call methods from the parent class. It’s particularly useful in constructors:

super-function.py
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def display(self):
return f"Name: {self.name}, Age: {self.age}"
class Student(Person):
def __init__(self, name, age, student_id):
super().__init__(name, age) # Call parent constructor
self.student_id = student_id
def display(self):
parent_info = super().display() # Call parent method
return f"{parent_info}, Student ID: {self.student_id}"
student = Student("Alice", 20, "S12345")
print(student.display()) # Name: Alice, Age: 20, Student ID: S12345

Using super() with Multiple Inheritance

With multiple inheritance, super() follows the Method Resolution Order (MRO):

super-multiple.py
class A:
def method(self):
return "A"
class B(A):
def method(self):
return f"B -> {super().method()}"
class C(A):
def method(self):
return f"C -> {super().method()}"
class D(B, C):
def method(self):
return f"D -> {super().method()}"
obj = D()
print(obj.method()) # D -> B -> C -> A

Method Resolution Order (MRO)

Method Resolution Order (MRO) determines the order in which Python searches for methods in inheritance hierarchies. Python uses the C3 linearization algorithm.

You can view the MRO using the __mro__ attribute or mro() method:

mro-example.py
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>,
# <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
print(D.mro())
# Same output as above

Understanding MRO in Multiple Inheritance

mro-multiple.py
class First:
def method(self):
return "First"
class Second:
def method(self):
return "Second"
class Third(First, Second):
pass
class Fourth(Second, First):
pass
obj1 = Third()
obj2 = Fourth()
print(obj1.method()) # First (First comes before Second in MRO)
print(obj2.method()) # Second (Second comes before First in MRO)
print(Third.__mro__)
# (<class '__main__.Third'>, <class '__main__.First'>,
# <class '__main__.Second'>, <class 'object'>)

The isinstance() and issubclass() Functions

  • isinstance(obj, class): Checks if an object is an instance of a class or its subclasses
  • issubclass(class1, class2): Checks if a class is a subclass of another class
isinstance-issubclass.py
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
cat = Cat()
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True (Dog inherits from Animal)
print(isinstance(dog, Cat)) # False
print(issubclass(Dog, Animal)) # True
print(issubclass(Cat, Animal)) # True
print(issubclass(Dog, Cat)) # False

Accessing Parent Class Attributes

You can access parent class attributes directly or through super():

parent-attributes.py
class Parent:
class_var = "Parent class variable"
def __init__(self):
self.instance_var = "Parent instance variable"
class Child(Parent):
class_var = "Child class variable"
def __init__(self):
super().__init__()
self.instance_var = "Child instance variable"
def show_parent_class_var(self):
return Parent.class_var # Access parent class variable directly
def show_parent_instance_var(self):
return super().instance_var # Access parent instance variable
child = Child()
print(child.instance_var) # Child instance variable
print(child.show_parent_instance_var()) # Parent instance variable
print(child.class_var) # Child class variable
print(child.show_parent_class_var()) # Parent class variable

Overriding Special Methods

You can override special methods (magic methods) in child classes:

override-special.py
class Animal:
def __init__(self, name):
self.name = name
def __str__(self):
return f"Animal: {self.name}"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
def __str__(self):
return f"Dog: {self.name} ({self.breed})"
dog = Dog("Buddy", "Golden Retriever")
print(dog) # Dog: Buddy (Golden Retriever)

Complete Example: Employee Hierarchy

employee-hierarchy.py
class Employee:
company_name = "Tech Corp"
employee_count = 0
def __init__(self, name, employee_id):
self.name = name
self.employee_id = employee_id
Employee.employee_count += 1
def get_info(self):
return f"ID: {self.employee_id}, Name: {self.name}"
@classmethod
def get_employee_count(cls):
return cls.employee_count
class Manager(Employee):
def __init__(self, name, employee_id, department):
super().__init__(name, employee_id)
self.department = department
def get_info(self):
base_info = super().get_info()
return f"{base_info}, Department: {self.department}, Role: Manager"
class Developer(Employee):
def __init__(self, name, employee_id, programming_language):
super().__init__(name, employee_id)
self.programming_language = programming_language
def get_info(self):
base_info = super().get_info()
return f"{base_info}, Language: {self.programming_language}, Role: Developer"
class SeniorDeveloper(Developer):
def __init__(self, name, employee_id, programming_language, years_experience):
super().__init__(name, employee_id, programming_language)
self.years_experience = years_experience
def get_info(self):
base_info = super().get_info()
return f"{base_info}, Experience: {self.years_experience} years, Level: Senior"
manager = Manager("Alice", "M001", "Engineering")
dev1 = Developer("Bob", "D001", "Python")
dev2 = SeniorDeveloper("Charlie", "D002", "Python", 5)
print(manager.get_info())
# ID: M001, Name: Alice, Department: Engineering, Role: Manager
print(dev1.get_info())
# ID: D001, Name: Bob, Language: Python, Role: Developer
print(dev2.get_info())
# ID: D002, Name: Charlie, Language: Python, Role: Developer, Experience: 5 years, Level: Senior
print(f"Total employees: {Employee.get_employee_count()}") # 3

Diamond Problem in Multiple Inheritance

Python handles the diamond problem (when a class inherits from two classes that both inherit from the same base class) using MRO:

diamond-problem.py
class A:
def method(self):
return "A"
class B(A):
def method(self):
return f"B -> {super().method()}"
class C(A):
def method(self):
return f"C -> {super().method()}"
class D(B, C):
def method(self):
return f"D -> {super().method()}"
obj = D()
print(obj.method()) # D -> B -> C -> A
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>,
# <class '__main__.A'>, <class 'object'>)

Notice that A appears only once in the MRO, preventing the diamond problem.

Exercises

Exercise 1: Basic Inheritance

Create a parent class Vehicle with brand and model attributes, and a start() method that returns “Vehicle started”. Create a child class Car that inherits from Vehicle and adds a honk() method. Read brand and model, create a Car object, and call both methods.

Basic Inheritance

Checks: 0 times
Answer:
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def start(self):
return "Vehicle started"
class Car(Vehicle):
def honk(self):
return "Beep beep!"
brand = input()
model = input()
car = Car(brand, model)
print(car.start())
print(car.honk())

Exercise 2: Method Overriding

Create a parent class Shape with an area() method that returns 0. Create child classes Rectangle and Circle that override area() to calculate their respective areas. Read values and create objects, then print their areas.

Method Overriding

Checks: 0 times
Answer:
import math
class Shape:
def area(self):
return 0
class Rectangle(Shape):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
length = float(input())
width = float(input())
radius = float(input())
rect = Rectangle(length, width)
circle = Circle(radius)
print(f"Rectangle area: {rect.area()}")
print(f"Circle area: {circle.area():.2f}")

Exercise 3: Using super()

Create a parent class Person with name and age, and a display() method. Create a child class Student that adds student_id and overrides display() to include the student ID using super().

Using super()

Checks: 0 times
Answer:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def display(self):
return f"Name: {self.name}, Age: {self.age}"
class Student(Person):
def __init__(self, name, age, student_id):
super().__init__(name, age)
self.student_id = student_id
def display(self):
return f"{super().display()}, Student ID: {self.student_id}"
name = input()
age = int(input())
student_id = input()
student = Student(name, age, student_id)
print(student.display())

Exercise 4: Multiple Inheritance

Create two parent classes: Flyable with a fly() method and Swimmable with a swim() method. Create a child class Duck that inherits from both and has a name attribute. Read the name and call all three methods.

Multiple Inheritance

Checks: 0 times
Answer:
class Flyable:
def fly(self):
return "Flying high!"
class Swimmable:
def swim(self):
return "Swimming deep!"
class Duck(Flyable, Swimmable):
def __init__(self, name):
self.name = name
def quack(self):
return f"{self.name} says quack!"
name = input()
duck = Duck(name)
print(duck.fly())
print(duck.swim())
print(duck.quack())

Exercise 5: isinstance and issubclass

Create a parent class Animal and child classes Dog and Cat. Create a Dog object and use isinstance() to check if it’s an instance of Dog, Animal, and Cat. Use issubclass() to check if Dog and Cat are subclasses of Animal.

isinstance and issubclass

Checks: 0 times
Answer:
class Animal:
pass
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
print(isinstance(dog, Dog))
print(isinstance(dog, Animal))
print(isinstance(dog, Cat))
print(issubclass(Dog, Animal))
print(issubclass(Cat, Animal))

Course Progress

Section 50 of 61

Back to Course