Python Inheritance, MRO & Polymorphism
Inheritance allows a new class (Child / Subclass) to inherit attributes and methods from an existing class (Parent / Base Class), establishing an "Is-A" relationship.
Constructor Chaining with super(): When a child class overrides __init__, it MUST call super().__init__(...) to initialize the parent class attributes in memory properly:
class Vehicle:
"""Base class for all vehicles."""
def __init__(self, brand, model, base_price):
self.brand = brand
self.model = model
self.base_price = base_price
def get_info(self):
return f"{self.brand} {self.model} | Base Price: โน{self.base_price:,.2f}"
class ElectricCar(Vehicle):
"""Subclass inheriting from Vehicle."""
def __init__(self, brand, model, base_price, battery_kwh):
# super() invokes Vehicle's __init__ method:
super().__init__(brand, model, base_price)
self.battery_kwh = battery_kwh
# Method Overriding: Specialized description for electric cars
def get_info(self):
parent_info = super().get_info()
return f"โก [EV] {parent_info} | Battery: {self.battery_kwh} kWh"
def charge(self):
return f"๐ Charging {self.brand} {self.model}'s {self.battery_kwh} kWh battery to 100%..."
# Instantiate ElectricCar:
ev = ElectricCar("Tata", "Nexon EV", 1450000, 40.5)
print(ev.get_info())
print(ev.charge())
ElectricCar reuses brand, model, and base_price logic from Vehicle without rewriting a single line of boilerplate code.
Unlike languages like Java which prohibit multiple class inheritance, Python natively supports Multiple Inheritance (a class inheriting from two or more parent classes).
When methods with identical names exist across multiple parents (the classic Diamond Problem), Python resolves which method to invoke using a deterministic algorithm called C3 Linearization (Method Resolution Order - MRO).
You can inspect any class's lookup hierarchy using ClassName.__mro__ or ClassName.mro():
class Flyer:
def move(self):
return "๐ฆ
Flying through the clouds!"
class Swimmer:
def move(self):
return "๐ Swimming through the ocean!"
# Duck inherits from BOTH Flyer and Swimmer:
class Duck(Flyer, Swimmer):
def quack(self):
return "๐ฆ Quack Quack!"
donald = Duck()
# Because Flyer is listed FIRST in class Duck(Flyer, Swimmer), Flyer.move() wins:
print("Donald's move:", donald.move())
print("Donald's quack:", donald.quack())
# Inspecting the exact Method Resolution Order (MRO):
print("\n--- Duck Method Resolution Order (MRO) ---")
for idx, cls in enumerate(Duck.__mro__, 1):
print(f"{idx}. {cls.__name__}")
The resolution order is Duck -> Flyer -> Swimmer -> object. Python searches left-to-right through the base classes.
Polymorphism means "many forms" โ the ability of different objects to respond to the same method call in their own specialized way.
In Python, polymorphism is driven by Duck Typing: "If it walks like a duck and quacks like a duck, it's a duck." Python does not check if objects belong to the same inheritance tree; as long as they implement the expected method name, they can be processed interchangeably!
class CreditCardPayment:
def process_payment(self, amount):
return f"๐ณ Charged โน{amount:,.2f} via Credit Card Gateway."
class UPIPayment:
def process_payment(self, amount):
return f"๐ฑ Transferred โน{amount:,.2f} instantly via UPI (GPay/PhonePe)."
class CryptoPayment:
def process_payment(self, amount):
return f"๐ช Transferred โน{amount:,.2f} worth of Bitcoin to wallet."
# Polymorphic Checkout Processor:
def checkout(payment_provider, bill_amount):
# Works with ANY object that has a process_payment() method!
print(payment_provider.process_payment(bill_amount))
# Process orders with different payment handlers polymorphically:
payment_methods = [
CreditCardPayment(),
UPIPayment(),
CryptoPayment()
]
print("--- ๐ Processing Polymorphic Checkout Transactions ---")
for method in payment_methods:
checkout(method, 2499.00)
You can add a new payment method (e.g. NetBankingPayment) without modifying a single line of the existing checkout() function!
If a child class overrides __init__ without calling super().__init__(), parent class attributes are never initialized in memory, causing AttributeError when calling inherited methods.
Create a base class Shape with an area() method, and subclasses Rectangle(length, width) and Circle(radius) that implement area().
import math
class Rectangle:
def __init__(self, l, w): self.l, self.w = l, w
def area(self): return self.l * self.w
class Circle:
def __init__(self, r): self.r = r
def area(self): return math.pi * (self.r ** 2)
shapes = [Rectangle(10, 5), Circle(7)]
for s in shapes:
print(f"Shape Area: {s.area():.2f}")
Q What is the Diamond Problem in Multiple Inheritance?
The Diamond Problem occurs when class D inherits from B and C, which both inherit from A. If B and C override a method from A, ambiguity arises as to which method D inherits. Python solves this cleanly with C3 Linearization (MRO).
Q What is the difference between isinstance() and type() in inheritance?
type(obj) == BaseClass returns False for subclass instances. isinstance(obj, BaseClass) returns True for both BaseClass and any of its derived subclasses (polymorphically aware).
Q Can a class inherit from built-in types like list or dict?
Yes! In Python, you can subclass built-in types (e.g. class CustomList(list):) to add specialized validation or custom methods.