Skip to content

Inheritance in Python

What is Inheritance?

Inheritance lets a new class (the CHILD, also called subclass or derived class) reuse the variables and methods of an existing class (the PARENT, also called superclass or base class).

class Parent:
    ...
class Child(Parent):     <- Child inherits everything from Parent
    ...

Why Use It?

  • CODE REUSE - write common code once in the parent.
  • EXTENSIBILITY - add new features in the child without touching the parent.
  • "IS-A" RELATION - a Car IS A Vehicle, a Manager IS AN Employee.

Types of Inheritance

Single        A -> B
Multilevel    A -> B -> C
Hierarchical  A -> B,  A -> C,  A -> D
Multiple      A + B -> C
Hybrid        a mix of the above (e.g. the "diamond")

Single Inheritance (Vehicle -> Car)

The child gets ALL the parent's methods for free and can add new ones. Industry example: a ride-booking app with a general Vehicle class.

class Vehicle:                                   # PARENT
    def __init__(self, reg_no, brand):
        self.reg_no = reg_no
        self.brand = brand

    def start(self):
        print(f"  {self.brand} [{self.reg_no}] engine started")

    def stop(self):
        print(f"  {self.brand} [{self.reg_no}] stopped")


class Car(Vehicle):                              # CHILD
    def play_music(self):                        # NEW method, only in Car
        print(f"  {self.brand} is playing music")


car = Car("TS09AB1234", "Honda")    # uses Vehicle's __init__ (inherited)
car.start()                         # inherited from Vehicle
car.play_music()                    # Car's own method
car.stop()                          # inherited from Vehicle

super() - Calling the Parent Constructor

When a child needs EXTRA data, it writes its own __init__. It must then call the parent's __init__ with super().__init__(...) so the parent's data is also set. If you forget, the parent's attributes will be missing.

class Bike(Vehicle):
    def __init__(self, reg_no, brand, has_carrier):
        super().__init__(reg_no, brand)     # let the parent set reg_no and brand
        self.has_carrier = has_carrier      # the child sets its own extra data

    def info(self):
        print(f"  Bike {self.brand} [{self.reg_no}] carrier={self.has_carrier}")


Bike("AP28XY9999", "Royal Enfield", True).info()


# What happens if we FORGET super().__init__ ?
class BrokenBike(Vehicle):
    def __init__(self, reg_no, brand, has_carrier):
        self.has_carrier = has_carrier      # parent __init__ never ran!


broken = BrokenBike("X1", "Hero", False)
try:
    broken.start()
except AttributeError as err:
    print("  ERROR (forgot super().__init__):", err)

Method Overriding and Extending

OVERRIDING : the child writes a method with the SAME name, which

REPLACES the parent's version for child objects.

EXTENDING : the child overrides, but still calls super().method()

to keep the parent's behaviour and add more.

Industry example: notification sending and logging.

class Notification:
    def __init__(self, to, message):
        self.to = to
        self.message = message

    def send(self):
        print(f"  [Generic] To {self.to}: {self.message}")


class EmailNotification(Notification):
    def send(self):                                      # OVERRIDE (replace)
        print(f"  [Email] To {self.to} | Subject: Alert | Body: {self.message}")


class AuditedNotification(Notification):
    def send(self):                                      # EXTEND
        print("  [Audit] writing to audit log...")
        super().send()                                   # run the parent's version too
        print("  [Audit] done")


Notification("user@mail.com", "Welcome!").send()
EmailNotification("user@mail.com", "Password changed").send()
AuditedNotification("admin@mail.com", "New login detected").send()

Multilevel Inheritance (Person -> Employee -> Manager)

A chain: a grandchild inherits from a child, which inherits from a parent. The Manager gets everything from Employee AND from Person. Industry example: company HR hierarchy.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        print(f"  Hi, I am {self.name}, {self.age} years old")


class Employee(Person):
    def __init__(self, name, age, emp_id, salary):
        super().__init__(name, age)
        self.emp_id = emp_id
        self.salary = salary

    def work(self):
        print(f"  {self.name} ({self.emp_id}) is working")


class Manager(Employee):
    def __init__(self, name, age, emp_id, salary, team):
        super().__init__(name, age, emp_id, salary)
        self.team = team

    def conduct_meeting(self):
        print(f"  {self.name} is meeting the team: {', '.join(self.team)}")


mgr = Manager("Suresh", 40, "M-01", 150000, ["Anu", "Bala", "Chitra"])
mgr.introduce()          # from Person   (grandparent)
mgr.work()               # from Employee (parent)
mgr.conduct_meeting()    # from Manager  (itself)

Hierarchical Inheritance (One Parent, Many Children)

Several child classes share ONE parent. The common logic lives in the parent, and each child adds its own rules. Industry example: banking - Savings and Current accounts.

class Account:
    def __init__(self, acc_no, balance):
        self.acc_no = acc_no
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        print(f"  {self.acc_no}: deposited {amount}, balance {self.balance}")


class SavingsAccount(Account):
    INTEREST_RATE = 0.04

    def add_interest(self):
        interest = self.balance * self.INTEREST_RATE
        self.balance += interest
        print(f"  {self.acc_no}: interest {interest:.2f} added, balance {self.balance:.2f}")


class CurrentAccount(Account):
    OVERDRAFT_LIMIT = 50000

    def withdraw(self, amount):
        if amount > self.balance + self.OVERDRAFT_LIMIT:
            print(f"  {self.acc_no}: overdraft limit exceeded")
        else:
            self.balance -= amount
            print(f"  {self.acc_no}: withdrew {amount}, balance {self.balance}")


s = SavingsAccount("SB-100", 10000)
c = CurrentAccount("CA-200", 10000)
s.deposit(5000)           # shared method from Account
c.deposit(5000)           # shared method from Account
s.add_interest()          # only Savings has this
c.withdraw(40000)         # only Current has overdraft

Multiple Inheritance (Camera + Phone -> Smartphone)

A child inherits from MORE THAN ONE parent at the same time. Industry example: a smartphone is both a phone and a camera.

class Camera:
    def take_photo(self):
        print("  Click! Photo captured")

    def device_type(self):
        return "Camera"


class Phone:
    def make_call(self, number):
        print(f"  Calling {number}...")

    def device_type(self):
        return "Phone"


class SmartPhone(Phone, Camera):          # order matters: Phone first
    pass


sp = SmartPhone()
sp.make_call("9000012345")    # from Phone
sp.take_photo()               # from Camera
# Both parents have device_type(). Which one wins?
# Python searches LEFT to RIGHT in the class definition -> Phone.
print("  device_type() ->", sp.device_type())

Hybrid / Diamond Inheritance and MRO

       BaseLogger
       /        \
FileLogger    CloudLogger
       \        /
      HybridLogger

MRO (Method Resolution Order) is the exact order in which Python searches classes for a method. See it with ClassName.__mro__ or ClassName.mro(). With super(), each class in the MRO runs ONCE, so BaseLogger is not called twice. Industry example: writing logs to a local file AND to the cloud.

class BaseLogger:
    def log(self, msg):
        print(f"  [Base]  formatting: {msg}")


class FileLogger(BaseLogger):
    def log(self, msg):
        print("  [File]  writing to app.log")
        super().log(msg)


class CloudLogger(BaseLogger):
    def log(self, msg):
        print("  [Cloud] uploading to cloud storage")
        super().log(msg)


class HybridLogger(FileLogger, CloudLogger):
    def log(self, msg):
        print("  [Hybrid] start")
        super().log(msg)


HybridLogger().log("User logged in")
print("  MRO:", " -> ".join(cls.__name__ for cls in HybridLogger.__mro__))

Mixins (A Very Common Industry Pattern)

A MIXIN is a small class that adds ONE extra ability. It is not meant to be used alone. We "mix" it into other classes with multiple inheritance. Django, Flask and many frameworks use mixins heavily.

import json
from datetime import datetime


class JsonMixin:
    def to_json(self):
        return json.dumps(self.__dict__)


class TimestampMixin:
    def stamp(self):
        self.updated_at = datetime.now().strftime("%Y-%m-%d %H:%M")


class User(JsonMixin, TimestampMixin):
    def __init__(self, username, email):
        self.username = username
        self.email = email


class ProductRecord(JsonMixin):            # reuses only the JSON feature
    def __init__(self, sku, price):
        self.sku = sku
        self.price = price


u = User("sudheer", "s@mmc.com")
u.stamp()
print("  User JSON   :", u.to_json())
print("  Product JSON:", ProductRecord("P-9", 499).to_json())

isinstance() and issubclass()

isinstance(obj, Class)   -> is this object of that class (or its child)?

issubclass(Child, Parent)-> does this class inherit from that class?

print("  isinstance(mgr, Manager)  :", isinstance(mgr, Manager))
print("  isinstance(mgr, Person)   :", isinstance(mgr, Person), " <- a Manager IS A Person")
print("  isinstance(car, Bike)     :", isinstance(car, Bike))
print("  issubclass(Manager, Employee):", issubclass(Manager, Employee))
print("  issubclass(Car, Vehicle)     :", issubclass(Car, Vehicle))
print("  issubclass(Vehicle, Car)     :", issubclass(Vehicle, Car), " <- a parent is not a child")
print("  Every class inherits from object:", issubclass(Car, object))

Private Members and Inheritance

__private names are mangled with the PARENT's class name, so the child cannot use them directly. Use _protected names for data that children are meant to share.

class Wallet:
    def __init__(self, amount):
        self.__secret_amount = amount     # private -> _Wallet__secret_amount
        self._shared_amount = amount      # protected -> children may use


class RewardsWallet(Wallet):
    def show(self):
        print("  Protected value from child:", self._shared_amount)
        try:
            print(self.__secret_amount)   # looks for _RewardsWallet__secret_amount
        except AttributeError as err:
            print("  ERROR accessing parent's private value:", err)


RewardsWallet(500).show()

Inheritance (IS-A) vs Composition (HAS-A)

Use inheritance only for a true "IS-A" relation.

Car IS A Vehicle          -> inheritance
Car HAS AN Engine         -> composition (keep an Engine object inside)

Rule of thumb in industry: "Prefer composition over inheritance" when the relation is HAS-A. It keeps classes loosely connected.

class Engine:
    def __init__(self, hp):
        self.hp = hp

    def ignite(self):
        print(f"  {self.hp} HP engine roaring")


class ElectricCar(Vehicle):                  # IS-A Vehicle (inheritance)
    def __init__(self, reg_no, brand, hp):
        super().__init__(reg_no, brand)
        self.engine = Engine(hp)             # HAS-AN Engine (composition)

    def start(self):
        self.engine.ignite()                 # delegate work to the engine
        super().start()


ElectricCar("KA01EV0001", "Tata", 140).start()

Quick Revision

  • class Child(Parent): -> Child reuses Parent's code.
  • Always call super().__init__(...) when the child has its own __init__.
  • Overriding = same method name in child. Use super().method() to extend.
  • Types: Single, Multilevel, Hierarchical, Multiple, Hybrid.
  • MRO decides which method runs in multiple inheritance (ClassName.__mro__).
  • Mixins add small reusable features.
  • IS-A -> inheritance, HAS-A -> composition.

Practice Questions

  1. Create Shape -> Rectangle -> Square (multilevel) with an area() method.
  2. Create Animal with children Dog, Cat, Cow, each overriding sound().
  3. Create Teacher(Person) and Student(Person), then TeachingAssistant(Teacher, Student).
  4. Create a CsvExportMixin and add it to two unrelated classes.