Logo OnlineCBT
PYTHON Tutorial
PYTHON · LESSON 35

Inheritance and Polymorphism

Reuse interfaces while avoiding fragile inheritance hierarchies.

Level: Beginner to Advanced
Duration: ~60 Mins Deep-Dive
Updated: 26 Jul 2026

Lesson 35: Inheritance and Polymorphism

1. Introduction to Class Hierarchies and Code Reuse

In Lesson 34, we explored Encapsulation—protecting internal object state using managed properties and access modifiers. However, as domain software architectures grow in complexity, writing completely separate classes for every real-world entity creates massive code duplication.

Consider building an enterprise payment system: processing credit cards, wire transfers, crypto payments, and UPI transfers shares significant core infrastructure (transaction IDs, timestamps, user authentication, fee logs). Writing separate, isolated classes for each payment gateway duplicates logic and violates the **DRY (Don't Repeat Yourself)** principle.

To solve this, Object-Oriented Programming provides two complementary structural pillars:

  • Inheritance: A mechanism allowing specialized Child (Sub) classes to derive attributes and behavior from a generalized Parent (Base) class, promoting maximum code reuse.
  • Polymorphism: The ability for different object types to respond to the exact same method call signature in specialized ways, allowing callers to treat specialized instances through a unified interface.

2. Single Inheritance and Method Overriding using `super()`

1. Parent and Child Relationship Syntax

A child class inherits from a parent class by specifying the parent class name inside parentheses during child class declaration: class ChildClass(ParentClass):.

2. Method Overriding and the `super()` Proxy Function

A child class can **override** a method defined in its parent class by re-declaring the method with the exact same name. To preserve and extend parent initialization logic rather than replacing it entirely, Python provides the super() built-in function.

Role of `super().__init__()`: Calling super().__init__(args...) delegates attribute initialization to the parent class constructor first, avoiding code repetition before the child class initializes its own unique specialized attributes!
INHERITANCE_SUPER_DEMO.PY
class BasePaymentGateway:
    """Parent (Base) class establishing common payment infrastructure."""
    def __init__(self, transaction_id: str, amount: float):
        self.transaction_id = transaction_id
        self.amount = amount
        self.status = "PENDING"

    def process_payment((self) -> bool:
        """Base execution template method."""
        print(f"[BASE GATEWAY] Initializing transaction #{self.transaction_id} for ${self.amount:,.2f}")
        return True


class CreditCardPayment(BasePaymentGateway):
    """Child (Sub) class specializing in credit card transactions."""
    def __init__(self, transaction_id: str, amount: float, card_number: str, cvv: str):
        # 1. Delegate base initialization to Parent class via super()
        super().__init__(transaction_id, amount)
        # 2. Initialize child-specific specialized attributes
        self.card_number_masked = f"****-****-****-{card_number[-4:]}"
        self._cvv = cvv

    # OVERRIDING Parent Method to add specialized business logic
    def process_payment(self) -> bool:
        super().process_payment()  # Invoke base parent behavior first
        if len(self._cvv) != 3:
            self.status = "REJECTED"
            print(f"[CREDIT CARD] ERROR: Invalid CVV security code for Card {self.card_number_masked}!")
            return False
        
        self.status = "COMPLETED"
        print(f"[CREDIT CARD] SUCCESS: Charged ${self.amount:,.2f} to Card {self.card_number_masked}.")
        return True

# Testing Inheritance and Method Overriding
tx1 = CreditCardPayment("TX_8801", 450.00, "1234567890123456", "999")
tx1.process_payment()
print(f"Final Status: {tx1.status}")
OUTPUT
[BASE GATEWAY] Initializing transaction #TX_8801 for $450.00
[CREDIT CARD] SUCCESS: Charged $450.00 to Card ****-****-****-3456.
Final Status: COMPLETED

3. Polymorphism and Duck Typing in Python

Polymorphism (from Greek, meaning "many forms") allows objects of different classes to be accessed via a uniform method interface. Callers do not need to know the explicit concrete class type of an object—they simply invoke a common method, and each object executes its own specialized implementation.

1. Python's "Duck Typing" Philosophy

In statically-typed languages (like C++ or Java), polymorphism requires strict interface implementations or explicit abstract base class inheritance.

Python utilizes Duck Typing: "If it walks like a duck and quacks like a duck, it's a duck!" If multiple objects implement a method named execute(), Python executes that method regardless of whether the objects share a common parent class inheritance tree!

POLYMORPHISM_DUCK_TYPING_DEMO.PY
class CryptoPayment:
    """Un-related standalone class implementing identical polymorphic interface."""
    def __init__(self, wallet_address: str, amount: float):
        self.wallet_address = wallet_address
        self.amount = amount

    def process_payment(self) -> bool:
        print(f"[CRYPTO] Transferred ${self.amount:,.2f} USD equivalent to Blockchain Wallet '{self.wallet_address[:8]}...'")
        return True

class UPIPayment(BasePaymentGateway):
    """Subclass inheriting from BasePaymentGateway."""
    def __init__(self, transaction_id: str, amount: float, upi_id: str):
        super().__init__(transaction_id, amount)
        self.upi_id = upi_id

    def process_payment(self) -> bool:
        print(f"[UPI GATEWAY] Transferred ${self.amount:,.2f} instantly to UPI VPA '{self.upi_id}'")
        self.status = "COMPLETED"
        return True

# POLYMORPHIC BATCH PROCESSOR
def execute_batch_checkout(payment_stream: list):
    """
    Polymorphic Function: Accepts any object providing a process_payment() method!
    Demonstrates Duck Typing in action.
    """
    print("=== Executing Batch Polymorphic Processing Pipeline ===")
    for idx, payment_item in enumerate(payment_stream, start=1):
        print(f"\nItem #{idx}:")
        payment_item.process_payment()  # Dynamic Method Dispatch!

# Heterogeneous list containing totally different payment class objects
payments = [
    CreditCardPayment("TX_901", 120.00, "1111222233334444", "123"),
    UPIPayment("TX_902", 50.00, "merchant@okbank"),
    CryptoPayment("0x71C...3A", 1250.00)  # Duck Typing: Shares method interface without parent class!
]

execute_batch_checkout(payments)
OUTPUT
=== Executing Batch Polymorphic Processing Pipeline ===

Item #1:
[BASE GATEWAY] Initializing transaction #TX_901 for $120.00
[CREDIT CARD] SUCCESS: Charged $120.00 to Card ****-****-****-4444.

Item #2:
[UPI GATEWAY] Transferred $50.00 instantly to UPI VPA 'merchant@okbank'

Item #3:
[CRYPTO] Transferred $1,250.00 USD equivalent to Blockchain Wallet '0x71C...'

4. Multiple Inheritance and Method Resolution Order (MRO)

Python supports Multiple Inheritance—allowing a single child class to inherit attributes and methods from multiple parent classes simultaneously: class Child(ParentA, ParentB):.

1. The Diamond Problem and C3 Linearization Algorithm

When multiple parent classes define a method with the exact same name, Python resolves method calls using a deterministic search order known as Method Resolution Order (MRO), powered by the **C3 Linearization Algorithm**.

Search Sequence: Child Class ---> Parent A (Left) ---> Parent B (Right) ---> Base Object
    
Inspecting Class MRO: You can inspect any class's exact method lookup hierarchy programmatically by calling ClassName.mro() or inspecting ClassName.__mro__!
MULTIPLE_INHERITANCE_MRO_DEMO.PY
class LoggerMixin:
    """Mixin class providing logging capabilities."""
    def log_event(self, message: str):
        print(f"[AUDIT LOG]: {message}")


class EncryptionMixin:
    """Mixin class providing payload encryption capabilities."""
    def encrypt_data(self, raw_str: str) -> str:
        return f"ENC({raw_str[::-1]})"


# Multiple Inheritance: Inherits from Base Class + Two Mixin Classes
class SecureServiceGateway(BasePaymentGateway, LoggerMixin, EncryptionMixin):
    def __init__(self, transaction_id: str, amount: float):
        super().__init__(transaction_id, amount)

    def execute_secure_tx(self):
        encrypted_id = self.encrypt_data(self.transaction_id)
        self.log_event(f"Processing Encrypted Transaction: {encrypted_id}")

# Instantiating Multiple Inheritance Object
service = SecureServiceGateway("TX_99001", 5000.0)
service.execute_secure_tx()

print("\n=== Method Resolution Order (MRO) Hierarchy ===")
for rank, class_obj in enumerate(SecureServiceGateway.mro(), start=1):
    print(f"MRO Priority #{rank}: {class_obj.__name__}")
OUTPUT
[AUDIT LOG]: Processing Encrypted Transaction: ENC(10099_XT)

=== Method Resolution Order (MRO) Hierarchy ===
MRO Priority #1: SecureServiceGateway
MRO Priority #2: BasePaymentGateway
MRO Priority #3: LoggerMixin
MRO Priority #4: EncryptionMixin
MRO Priority #5: object

5. Combining Class Hierarchies with `match-case` Pattern Matching

Python 3.10+ Class Structural Pattern Matching evaluates polymorphic inheritance trees dynamically, allowing clean dispatchers to route inherited child instances based on concrete class types and specialized attributes.

MATCH_CASE_INHERITANCE_DISPATCHER.PY
def process_polymorphic_event(payment_obj: BasePaymentGateway | object):
    """
    Routes polymorphic inherited class instances using Structural Pattern Matching.
    Demonstrates class type evaluation and attribute extraction.
    """
    match payment_obj:
        case CreditCardPayment(status="COMPLETED", amount=amt, card_number_masked=card):
            return f"MATCH: Credit Card Sale confirmed! ${amt:,.2f} processed via {card}"
            
        case UPIPayment(status="COMPLETED", upi_id=vpa, amount=amt):
            return f"MATCH: Instant UPI Settlement confirmed! ${amt:,.2f} transferred to {vpa}"
            
        case BasePaymentGateway(status="REJECTED", transaction_id=tid):
            return f"MATCH_REJECTED: Base Gateway transaction #{tid} failed validation!"
            
        case CryptoPayment(amount=amt) as crypto_obj:
            return f"MATCH_DUCK_TYPED: Standalone Crypto object for ${amt:,.2f} matched!"
            
        case _:
            return "ERROR: Unrecognized object instance schema type."

# Testing the pattern dispatcher
cc_tx = CreditCardPayment("TX_1", 200.0, "1234567890123456", "999")
cc_tx.process_payment()

upi_tx = UPIPayment("TX_2", 85.0, "user@upi")
upi_tx.process_payment()

print("\n--- Pattern Dispatcher Outputs ---")
print(process_polymorphic_event(cc_tx))
print(process_polymorphic_event(upi_tx))
OUTPUT
[BASE GATEWAY] Initializing transaction #TX_1 for $200.00
[CREDIT CARD] SUCCESS: Charged $200.00 to Card ****-****-****-3456.
[UPI GATEWAY] Transferred $85.00 instantly to UPI VPA 'user@upi'

--- Pattern Dispatcher Outputs ---
MATCH: Credit Card Sale confirmed! $200.00 processed via ****-****-****-3456
MATCH: Instant UPI Settlement confirmed! $85.00 transferred to user@upi

6. Frequently Asked Interview Questions with Answers

Q1: What is the purpose of the `super()` function in Python subclass methods?
Answer: super() returns a proxy object that delegates method calls to a parent or sibling class in the inheritance hierarchy. In subclass __init__() constructors, super().__init__() ensures parent initialization logic is executed prior to setting child-specific attributes.
Q2: What is Method Overriding in Python Object-Oriented Programming?
Answer: Method Overriding occurs when a child subclass provides a specific implementation of a method that is already defined in its parent base class, replacing or extending the parent's default behavior for instances of the child class.
Q3: How does "Duck Typing" enable Polymorphism in Python?
Answer: Duck Typing determines an object's suitability based on the presence of specific methods or properties rather than its explicit class inheritance hierarchy. If multiple distinct classes implement a method with the same signature (e.g., .process()), Python can invoke that method polymorphically on any of those objects.
Q4: What is Method Resolution Order (MRO), and how can you inspect it for a class?
Answer: MRO is the order in which Python searches parent classes for a method or attribute when called on an object instance. It uses the C3 Linearization algorithm. You can inspect a class's MRO by calling ClassName.mro() or inspecting ClassName.__mro__.
Q5: What is a Mixin Class in Python Multiple Inheritance?
Answer: A Mixin is a small, specialized class designed to provide extra reusable methods to child subclasses through multiple inheritance, without being intended for standalone instantiation itself.

7. Homework & Practical Assignments

Task 1: Employee Hierarchy with Method Overriding

Create a script named employee_inheritance_task.py inside your lesson_35 folder:

  • Define base class Employee(emp_id: str, name: str, base_salary: float) with method calculate_pay() -> float.
  • Define child class SalariedEmployee(emp_id, name, base_salary, bonus: float) that calls super().__init__() and overrides calculate_pay() to return base_salary + bonus.
  • Define child class HourlyEmployee(emp_id, name, hourly_rate: float, hours_worked: float) that overrides calculate_pay() to compute rate * hours.
  • Instantiate both employee objects and print their calculated pay using f-strings.

Task 2: Polymorphic Notification Router with `match-case`

Create a script named notification_router_task.py:

  • Define base class Notification(recipient: str, message: str) with method send().
  • Define subclasses EmailNotification(recipient, message, subject) and SMSNotification(recipient, message, phone_number) overriding send().
  • Write a dispatcher function using match-case Class Pattern Matching to inspect notification objects and route delivery statuses.
  • Test function with instances of both subclasses and display formatted outputs.

Task 3: Master Review Capstone Project — Enterprise Polymorphic Banking & Audit Engine (`enterprise_banking_os.py`)

Create a script named enterprise_banking_os.py inside your lesson_35 folder. This assignment tests and integrates **ALL concepts learned across Lessons 1 through 35**.

Project Architectural Requirements Specification:

  1. Inheritance & Polymorphism Architecture (Lesson 35):
    • Define base class BankAccount(account_number, owner, balance) with methods deposit(), withdraw(), and calculate_interest().
    • Define subclasses: SavingsAccount(interest_rate) and CheckingAccount(overdraft_limit) overriding interest and withdrawal logic using super().
    • Implement polymorphic batch processing over a collection of mixed account instances.
  2. Encapsulation, Properties & Validation (Lesson 34):
    • Protect internal balances using _balance protected fields and managed @property setters with negative value validation.
  3. Advanced OOP Methods & Constructors (Lessons 32-33):
    • Implement class method factory from_dict(cls, data) to instantiate accounts from dictionaries using cls(...).
    • Implement static methods for account ID formatting. Track total created accounts via class attributes.
  4. Observability & Diagnostics (Lesson 31):
    • Configure a logging FileHandler writing diagnostic events to banking_system.log.
    • Add internal developer assertions (assert) verifying non-negative balance invariants.
  5. Context Managers & Fault Tolerance (Lessons 29-30):
    • Build a custom class-based context manager BankTransactionSession(db_path) to manage file persistence safely.
    • Define custom exceptions: BankingError(Exception), OverdraftExceededError(BankingError). Wrap operations in 4-block try-except-else-finally suites.
  6. CSV & JSON Persistence Layer (Lessons 27-28):
    • Maintain storage directory bank_vault/ using pathlib.Path.
    • Persist account state to accounts_db.json and export audit reports to transactions_audit.csv.
  7. Nested Collections, Comprehensions & Data Structures (Lessons 21-26):
    • Maintain an in-memory registry dictionary mapping account numbers to Class Instances.
    • Use List and Dictionary Comprehensions to filter and map active accounts dynamically.
  8. Advanced Function Parameters & Scope (Lessons 15-17):
    • Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and *args / **kwargs logging.
  9. Indefinite & Definite Loops (Lessons 13-14):
    • Run the interactive CLI interface inside a continuous while True menu loop.
    • Iterate through output reports using for loops with enumerate() and .items().
  10. Pattern Matching CLI Dispatcher (Lesson 12):
    • Process user CLI command tokens inside a match-case block:
      • case ["CREATE", "SAVINGS", acc_num, owner, bal_str, rate_str] → Instantiate SavingsAccount object and persist.
      • case ["CREATE", "CHECKING", acc_num, owner, bal_str, limit_str] → Instantiate CheckingAccount object and persist.
      • case ["WITHDRAW", acc_num, amt_str] → Execute polymorphic withdrawal method on target account.
      • case ["AUDIT", "ACCOUNTS"] → Route polymorphic account instances using Class Pattern Matching.
      • case ["EXIT" | "QUIT"] → Terminate session safely using a sentinel flag.
      • case _ → Output command error message.
  11. Conditionals, Formatting & Foundations (Lessons 1-11):
    • Sanitize all inputs using .strip() and .upper().
    • Format tabular reports using f-strings with field width alignment specifiers (:<15, :>10) and currency formatting (:,.2f).

8. File & Workspace Directory Structure

Standard Course Workspace Layout:

Ensure all exercise files are stored inside their corresponding lesson directories:

python_mastery_course/
│
├── lesson_01/ ... lesson_34/
│
└── lesson_35/
    ├── inheritance_super_demo.py
    ├── polymorphism_duck_typing_demo.py
    ├── multiple_inheritance_mro_demo.py
    ├── match_case_inheritance_dispatcher.py
    ├── employee_inheritance_task.py   <-- (Task 1)
    ├── notification_router_task.py   <-- (Task 2)
    └── enterprise_banking_os.py       <-- (Task 3: Master Review Capstone)
        

9. What We Will Learn Next

Next Up: Lesson 36 — Magic Methods and Data Classes

Now that you master Inheritance and Polymorphism, we will explore customizing built-in operator behaviors and writing clean boilerplate-free data containers!

In the next lesson, we will cover:

  • Understanding Dunder (Double Underscore) Magic Methods in Python classes.
  • String Representations: __str__() (for human users) vs __repr__() (for developers).
  • Operator Overloading: Customizing + (__add__), == (__eq__), and < (__lt__).
  • Modern Boilerplate Reduction using Python 3.7+ `dataclasses` (@dataclass).
  • Combining Data Classes and Magic Methods with match-case pattern matching.

📝 Live Lesson Practice

HTML/CSS JavaScript Python C++ C PHP
⌨️ Practice Inputs (लाइव इनपुट भरें) (खाली होने पर RED, भरने पर GREEN underline)
💻 Code Editor (Monaco VS Code Engine)
👀 Live Preview
Address Contact

+91 7877547686

E-mail

onlinecbtportal@gmail.com

Helpline Number

+91 7877547686


Click To Download
Get it on Google Play

ऐप डाउनलोड करने
के लिए Google Play पर
उपलब्ध है

WhatsApp Chat