Polymorphism¶
Polymorphism allows objects of different types to be treated through a common interface, with each type implementing behavior in its own way.
What is Polymorphism¶
1. Many Forms¶
"Polymorphism" means "many forms"—same interface, different implementations.
2. Common Interface¶
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
3. Uniform Treatment¶
animals = [Dog("Buddy"), Cat("Whiskers")]
for animal in animals:
print(animal.speak())
Don't care about the specific type—just call the method.
Interface Definition¶
1. Placeholder Method¶
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass # placeholder
Signals that all subclasses should implement speak.
2. Method Override¶
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
Each subclass provides its own implementation.
3. Type Consistency¶
All Animal objects have a speak method.
Duck Typing¶
1. No Type Declaration¶
def make_speak(animal):
print(animal.speak())
No need to declare animal is an Animal.
2. Interface Matters¶
class Robot:
def speak(self):
return "Beep boop!"
make_speak(Robot()) # Works!
If it has speak(), it works.
3. Runtime Check¶
if hasattr(obj, 'speak'):
obj.speak()
Method Overriding¶
1. Replace Behavior¶
class Parent:
def greet(self):
return "Hello from Parent"
class Child(Parent):
def greet(self):
return "Hello from Child"
2. Extend Behavior¶
class Child(Parent):
def greet(self):
parent_msg = super().greet()
return f"{parent_msg} and Child"
3. Selective Override¶
class Parent:
def method_a(self):
return "A"
def method_b(self):
return "B"
class Child(Parent):
def method_a(self): # override only this
return "A from Child"
# method_b inherited
Operator Overloading¶
1. Special Methods¶
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
2. String Representation¶
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector(x={self.x}, y={self.y})"
3. Comparison¶
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __lt__(self, other):
return self.x < other.x
Real-World Example¶
1. SciPy Distributions¶
from scipy import stats
# Different distributions, same interface
obj = stats.norm() # Normal
# obj = stats.uniform() # Uniform
# obj = stats.expon() # Exponential
x = obj.rvs(10_000) # All have rvs()
2. Common Methods¶
samples = obj.rvs(n) # random samples
density = obj.pdf(x) # probability density
cumulative = obj.cdf(x) # cumulative distribution
3. Interchangeable¶
Can swap distributions without changing code.
Polymorphic Functions¶
1. Generic Processing¶
def process_shapes(shapes):
total_area = 0
for shape in shapes:
total_area += shape.area()
return total_area
2. Mixed Types¶
shapes = [
Rectangle(3, 4),
Circle(5),
Triangle(3, 4)
]
print(process_shapes(shapes))
3. No Type Checks¶
Don't need to check isinstance()—just call the method.
Benefits¶
1. Code Reusability¶
Write once, works for many types.
2. Extensibility¶
Add new types without changing existing code.
3. Clean Design¶
No complex conditional logic based on type.
Polymorphism vs Abstraction¶
1. Polymorphism (Informal)¶
class Animal:
def speak(self):
pass # no enforcement
Trust that subclasses implement correctly.
2. Abstraction (Formal)¶
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass # enforced!
Forces subclasses to implement.
3. Key Difference¶
Polymorphism = same interface, different behaviors. Abstraction = enforced contract with ABC.
Key Takeaways¶
- Polymorphism enables uniform interfaces.
- Different types implement methods differently.
- Duck typing: if it quacks, it's a duck.
- Method overriding enables custom behavior.
- Operator overloading uses special methods.
Runnable Example: polymorphism_demo.py¶
"""
Example 05: Polymorphism
Polymorphism means "many forms". It allows objects of different classes
to be treated as objects of a common parent class, and the correct method
is called based on the object's actual type.
"""
# ============================================================================
# Example 1: Basic Polymorphism
class PaymentMethod:
def __init__(self, name):
self.name = name
def process_payment(self, amount):
raise NotImplementedError("Subclass must implement this method")
def get_receipt(self, amount):
return f"Payment of ${amount:.2f} via {self.name}"
class CreditCard(PaymentMethod):
def __init__(self, card_number, cvv):
super().__init__("Credit Card")
self.card_number = card_number[-4:] # Store only last 4 digits
self.cvv = cvv
def process_payment(self, amount):
return f"Processing ${amount:.2f} on card ending in {self.card_number}"
class PayPal(PaymentMethod):
def __init__(self, email):
super().__init__("PayPal")
self.email = email
def process_payment(self, amount):
return f"Processing ${amount:.2f} via PayPal account {self.email}"
class Bitcoin(PaymentMethod):
def __init__(self, wallet_address):
super().__init__("Bitcoin")
self.wallet_address = wallet_address
def process_payment(self, amount):
btc_amount = amount / 30000 # Simplified conversion
return f"Processing {btc_amount:.6f} BTC to wallet {self.wallet_address[:8]}..."
# ============================================================================
# Example 2: Polymorphism with Different Return Types
class DataProcessor:
def process(self, data):
raise NotImplementedError
class TextProcessor(DataProcessor):
def process(self, data):
# Returns uppercase text
return data.upper()
class NumberProcessor(DataProcessor):
def process(self, data):
# Returns sum of numbers
return sum(data)
class ListProcessor(DataProcessor):
def process(self, data):
# Returns sorted list
return sorted(data)
# ============================================================================
# Example 3: Duck Typing (Python's Polymorphism)
class Duck:
def speak(self):
return "Quack!"
def swim(self):
return "Duck is swimming"
class Person:
def speak(self):
return "Hello!"
def swim(self):
return "Person is swimming"
class Robot:
def speak(self):
return "Beep boop!"
def swim(self):
return "Robot cannot swim (error: water damage)"
def make_it_speak_and_swim(entity):
"""
Duck typing: If it walks like a duck and quacks like a duck, it's a duck.
We don't check the type, we just try to call the methods.
"""
print(entity.speak())
print(entity.swim())
# ============================================================================
# Example 4: Polymorphism in Action - File System
class FileSystemItem:
def __init__(self, name):
self.name = name
def get_size(self):
raise NotImplementedError
def display(self, indent=0):
raise NotImplementedError
class File(FileSystemItem):
def __init__(self, name, size_kb):
super().__init__(name)
self.size_kb = size_kb
def get_size(self):
return self.size_kb
def display(self, indent=0):
return " " * indent + f"📄 {self.name} ({self.size_kb} KB)"
class Folder(FileSystemItem):
def __init__(self, name):
super().__init__(name)
self.items = []
def add_item(self, item):
self.items.append(item)
def get_size(self):
# Polymorphism: call get_size() on different types
return sum(item.get_size() for item in self.items)
def display(self, indent=0):
result = " " * indent + f"📁 {self.name}/\n"
for item in self.items:
result += item.display(indent + 1) + "\n"
return result.rstrip()
# Testing Polymorphism
if __name__ == "__main__":
print("=" * 70)
print("EXAMPLE 1: PAYMENT PROCESSING POLYMORPHISM")
print("=" * 70)
# Different payment methods, same interface
payment_methods = [
CreditCard("1234-5678-9012-3456", "123"),
PayPal("user@email.com"),
Bitcoin("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
]
total = 99.99
for payment in payment_methods:
print(f"\n{payment.name}:")
print(payment.process_payment(total))
print(payment.get_receipt(total))
print("\n" + "=" * 70)
print("EXAMPLE 2: DATA PROCESSING POLYMORPHISM")
print("=" * 70)
processors = [
(TextProcessor(), "hello world"),
(NumberProcessor(), [1, 2, 3, 4, 5]),
(ListProcessor(), [5, 2, 8, 1, 9])
]
for processor, data in processors:
print(f"\n{processor.__class__.__name__}:")
print(f" Input: {data}")
print(f" Output: {processor.process(data)}")
print("\n" + "=" * 70)
print("EXAMPLE 3: DUCK TYPING")
print("=" * 70)
entities = [Duck(), Person(), Robot()]
for entity in entities:
print(f"\n{entity.__class__.__name__}:")
make_it_speak_and_swim(entity)
print("\n" + "=" * 70)
print("EXAMPLE 4: FILE SYSTEM POLYMORPHISM")
print("=" * 70)
# Create file system structure
root = Folder("root")
docs = Folder("documents")
docs.add_item(File("report.pdf", 250))
docs.add_item(File("notes.txt", 15))
images = Folder("images")
images.add_item(File("photo1.jpg", 1200))
images.add_item(File("photo2.jpg", 1500))
root.add_item(docs)
root.add_item(images)
root.add_item(File("readme.md", 8))
# Polymorphism in action: both File and Folder have get_size() and display()
print(root.display())
print(f"\nTotal size: {root.get_size()} KB")
"""
KEY TAKEAWAYS:
1. Polymorphism allows treating different objects through a common interface
2. Different classes can implement the same method differently
3. The correct method is called based on the object's actual type (runtime)
4. Python uses "duck typing" - if it has the method, you can call it
5. Polymorphism makes code more flexible and maintainable
6. Common parent classes define the interface
BENEFITS OF POLYMORPHISM:
1. Flexibility: Easy to add new types without changing existing code
2. Maintainability: Changes to one class don't affect others
3. Extensibility: New classes can be added that work with existing code
4. Code Reusability: Same function/loop works with multiple types
5. Abstraction: Hide implementation details behind common interface
REAL-WORLD USES:
- Payment processing (multiple payment methods)
- File handling (different file types)
- Database connections (MySQL, PostgreSQL, MongoDB)
- UI components (buttons, inputs, dropdowns)
- Game entities (players, enemies, NPCs)
- Notification systems (email, SMS, push notifications)
"""