Understanding Inheritance in Python

Inheritance is a fundamental concept in object-oriented programming (OOP) that allows a class to inherit attributes and methods from another class. This promotes code reusability and helps in creating a hierarchical relationship between classes. Python supports different types of inheritance, each serving a different purpose in software development.

What is Inheritance?

Inheritance enables a class (child or derived class) to inherit the properties and behavior of another class (parent or base class). This reduces redundancy in the code and helps in better code organization.

Types of Inheritance in Python

Python supports several types of inheritance:

Single Inheritance

In single inheritance, a child class inherits from only one parent class.

class Animal:
    def speak(self):
        print("Animals make sounds.")

class Dog(Animal):
    def bark(self):
        print("Dog barks.")

dog = Dog()
dog.speak()  # Inherited from Animal
dog.bark()   # Defined in Dog
Python

Multiple Inheritance

In multiple inheritance, a child class inherits from multiple parent classes.

class Father:
    def skill(self):
        print("Good at mathematics.")

class Mother:
    def hobby(self):
        print("Loves painting.")

class Child(Father, Mother):
    def talent(self):
        print("Good at coding.")

c = Child()
c.skill()
c.hobby()
c.talent()
Python

Here, Child inherits from both Father and Mother, combining behaviors from both classes.

Multilevel Inheritance

In multilevel inheritance, a class inherits from another class, which itself is a child of another class.

class Grandparent:
    def legacy(self):
        print("Family legacy.")

class Parent(Grandparent):
    def wisdom(self):
        print("Parent's wisdom.")

class Child(Parent):
    def ambition(self):
        print("Child's ambition.")

c = Child()
c.legacy()
c.wisdom()
c.ambition()
Python

Here, Child inherits from Parent, and Parent inherits from Grandparent.

Hierarchical Inheritance

In hierarchical inheritance, multiple child classes inherit from the same parent class.

class Vehicle:
    def general_info(self):
        print("Vehicles help in transportation.")

class Car(Vehicle):
    def car_info(self):
        print("Cars have four wheels.")

class Bike(Vehicle):
    def bike_info(self):
        print("Bikes have two wheels.")

c = Car()
b = Bike()

c.general_info()
c.car_info()

b.general_info()
b.bike_info()
Python

Here, both Car and Bike inherit from Vehicle.

Hybrid Inheritance

Hybrid inheritance is a combination of multiple types of inheritance.

class A:
    def methodA(self):
        print("Method from Class A")

class B(A):
    def methodB(self):
        print("Method from Class B")

class C(A):
    def methodC(self):
        print("Method from Class C")

class D(B, C):
    def methodD(self):
        print("Method from Class D")

obj = D()
obj.methodA()
obj.methodB()
obj.methodC()
obj.methodD()
Python

Here, D inherits from both B and C, which in turn inherit from A.

Method Resolution Order (MRO)

When multiple inheritance is involved, Python follows the C3 Linearization (MRO) to determine the order in which methods are inherited. Read More

Overriding Methods in Inheritance

A child class can override the methods of its parent class.

class Parent:
    def show(self):
        print("This is Parent class.")

class Child(Parent):
    def show(self):
        print("This is Child class.")

obj = Child()
obj.show()  # Calls Child's method instead of Parent's
Python

Here, show() in Child overrides show() in Parent.

To access the parent method inside the child class, use super():

class Parent:
    def show(self):
        print("This is Parent class.")

class Child(Parent):
    def show(self):
        super().show()  # Calls Parent's method
        print("This is Child class.")

obj = Child()
obj.show()
Python

Using super() in Inheritance

The super() function allows you to call a method from the parent class inside the child class.

class A:
    def __init__(self):
        print("Constructor of A")

class B(A):
    def __init__(self):
        super().__init__()  # Calls A's constructor
        print("Constructor of B")

obj = B()


Output
Constructor of A
Constructor of B
Python

Behaviour of __init__ function

Case 1: __init__ in Parent Only

class Parent:
    def __init__(self):
        print("Parent init")

class Child(Parent):
    pass

c = Child()

'''
Parent init
'''
Python

Since Child doesn’t have its own __init__, it inherits and uses the Parent’s __init__

Case 2: __init__ in Child Only

class Parent:
    def greet(self):
        print("Hello from Parent")

class Child(Parent):
    def __init__(self):
        print("Child init")

c = Child()

'''
Child init

'''
Python

  • Parent doesn’t have __init__, so Child’s __init__ runs.
  • Parent methods (greet) are still inherited.

Case 3: __init__ in Both Parent and Child (No super())

class Parent:
    def __init__(self):
        print("Parent init")

class Child(Parent):
    def __init__(self):
        print("Child init")

c = Child()

'''
Child init
'''
Python

  • The Child’s __init__ overrides the Parent’s __init__.
  • The Parent’s __init__ is not called unless explicitly done

Case 4: __init__ in Both, Child Calls super()

class Parent:
    def __init__(self):
        print("Parent init")

class Child(Parent):
    def __init__(self):
        super().__init__()
        print("Child init")

c = Child()

Parent init
Child init
Python

  • super().__init__() calls the Parent’s constructor.
  • Then Child continues its own initialization.

Private and Protected Members in Inheritance

  • Protected members (_var): Accessible within the class and its subclasses.
  • Private members (__var): Not directly accessible outside the class.

In Python, private (__var) and protected (_var) members are just conventions and not strictly enforced like in some other languages (e.g., Java or C++).

class Parent:
    def __init__(self):
        self._protected = "Protected Attribute"
        self.__private = "Private Attribute"

class Child(Parent):
    def access(self):
        print(self._protected)  # Accessible
        # print(self.__private)  # Will cause an error

obj = Child()
obj.access()


print(obj._protected) # Protected Attribute
print(obj.__private) # Error
Python

Private attributes can be accessed using name mangling (_Parent__private).

What is Name Mangling in Python?

  • Name mangling is a mechanism used by Python to avoid accidental variable name conflicts in subclasses when dealing with private variables (variables prefixed with double underscores __).
  • When you declare a variable with double underscores (__var), Python automatically renames it by prefixing the class name to it. This makes it harder (but not impossible) to access the variable from outside the class.

How Name Mangling Works

Python changes __var to _ClassName__var, so it is still accessible if explicitly referenced using this pattern.

class Parent:
    def __init__(self):
        self.__private = "This is a private variable"

obj = Parent()
# print(obj.__private)  # AttributeError: 'Parent' object has no attribute '__private'
print(obj._Parent__private)  # Access using name mangling
Python

Conclusion

Inheritance is a powerful OOP concept in Python that promotes code reusability and scalability. Understanding its different types and features like method overriding, super(), and MRO helps in writing efficient and organized programs.

1 thought on “Understanding Inheritance in Python”

Leave a Comment