Python Inheritance, MRO & Polymorphism

๐Ÿ Python 3.12+ ๐ŸŸข Chapter 32 of 65 ๐Ÿ“‚ Phase 7: Object-Oriented Programming ๐Ÿ“… 2026 Edition
๐Ÿ“Œ Covered in this chapter: Single Inheritance ยท super() ยท Method Overriding ยท Multiple Inheritance ยท MRO (C3 Linearization) ยท Polymorphism & Duck Typing
Master code reuse and polymorphic design in Python: single and multiple inheritance, super() constructor chaining, method overriding, Method Resolution Order (MRO) with C3 linearization, and runtime polymorphism via Duck Typing.
1Single Inheritance & Constructor Chaining with super()

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:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Parent Class: Vehicle (brand, model, year) โ”‚ โ”‚ โ””โ”€โ”€ Method: describe() โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ [class ElectricCar(Vehicle)] โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ Child Class: ElectricCar (battery_capacity_kwh) โ”‚ โ”‚ โ””โ”€โ”€ Method: describe(), charge() โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
๐Ÿ’ป Example 1: Single Inheritance, super(), and Method Overriding
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())
๐Ÿ” Reusability:

ElectricCar reuses brand, model, and base_price logic from Vehicle without rewriting a single line of boilerplate code.

2Multiple Inheritance & Method Resolution Order (MRO)

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():

๐Ÿ’ป Example 2: Multiple Inheritance and MRO Hierarchy
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__}")
๐Ÿ” MRO Order:

The resolution order is Duck -> Flyer -> Swimmer -> object. Python searches left-to-right through the base classes.

3Polymorphism & Python's "Duck Typing" Principle

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!

๐Ÿ’ป Example 3: Polymorphism in Action via Duck Typing
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)
๐Ÿ” Extensibility (Open-Closed Principle):

You can add a new payment method (e.g. NetBankingPayment) without modifying a single line of the existing checkout() function!

โš ๏ธ Common Developer Pitfall: Forgetting to Call super().__init__() in Subclasses

If a child class overrides __init__ without calling super().__init__(), parent class attributes are never initialized in memory, causing AttributeError when calling inherited methods.

๐Ÿ’ป Hands-on Interactive Practice Challenge

Create a base class Shape with an area() method, and subclasses Rectangle(length, width) and Circle(radius) that implement area().

Python 3 Practice Challenge โ–ถ Run in Compiler
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}")
Run This Challenge in Online Python IDE โ†’
โ“ Frequently Asked Questions (FAQ)

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.

OC
Written by Our Compiler Technical Editorial Team
Reviewed for accuracy & tested on Python 3.12+ runtime ยท Last updated August 2026