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.
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!
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}")
[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!
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)
=== 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
ClassName.mro() or inspecting ClassName.__mro__!
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__}")
[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.
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))
[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
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.
.process()), Python can invoke that method polymorphically on any of those objects.
ClassName.mro() or inspecting ClassName.__mro__.
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 methodcalculate_pay() -> float. - Define child class
SalariedEmployee(emp_id, name, base_salary, bonus: float)that callssuper().__init__()and overridescalculate_pay()to returnbase_salary + bonus. - Define child class
HourlyEmployee(emp_id, name, hourly_rate: float, hours_worked: float)that overridescalculate_pay()to computerate * 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 methodsend(). - Define subclasses
EmailNotification(recipient, message, subject)andSMSNotification(recipient, message, phone_number)overridingsend(). - Write a dispatcher function using
match-caseClass 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:
- Inheritance & Polymorphism Architecture (Lesson 35):
- Define base class
BankAccount(account_number, owner, balance)with methodsdeposit(),withdraw(), andcalculate_interest(). - Define subclasses:
SavingsAccount(interest_rate)andCheckingAccount(overdraft_limit)overriding interest and withdrawal logic usingsuper(). - Implement polymorphic batch processing over a collection of mixed account instances.
- Define base class
- Encapsulation, Properties & Validation (Lesson 34):
- Protect internal balances using
_balanceprotected fields and managed@propertysetters with negative value validation.
- Protect internal balances using
- Advanced OOP Methods & Constructors (Lessons 32-33):
- Implement class method factory
from_dict(cls, data)to instantiate accounts from dictionaries usingcls(...). - Implement static methods for account ID formatting. Track total created accounts via class attributes.
- Implement class method factory
- Observability & Diagnostics (Lesson 31):
- Configure a
loggingFileHandler writing diagnostic events tobanking_system.log. - Add internal developer assertions (
assert) verifying non-negative balance invariants.
- Configure a
- 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-blocktry-except-else-finallysuites.
- Build a custom class-based context manager
- CSV & JSON Persistence Layer (Lessons 27-28):
- Maintain storage directory
bank_vault/usingpathlib.Path. - Persist account state to
accounts_db.jsonand export audit reports totransactions_audit.csv.
- Maintain storage directory
- 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.
- Advanced Function Parameters & Scope (Lessons 15-17):
- Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and
*args/**kwargslogging.
- Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and
- Indefinite & Definite Loops (Lessons 13-14):
- Run the interactive CLI interface inside a continuous
while Truemenu loop. - Iterate through output reports using
forloops withenumerate()and.items().
- Run the interactive CLI interface inside a continuous
- Pattern Matching CLI Dispatcher (Lesson 12):
- Process user CLI command tokens inside a
match-caseblock:case ["CREATE", "SAVINGS", acc_num, owner, bal_str, rate_str]→ InstantiateSavingsAccountobject and persist.case ["CREATE", "CHECKING", acc_num, owner, bal_str, limit_str]→ InstantiateCheckingAccountobject 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.
- Process user CLI command tokens inside a
- 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).
- Sanitize all inputs using
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)
OnlineCBT