🦎 What is Polymorphism?
The word polymorphism comes from Greek:- Poly = Many
- Morph = Forms
- For a TV: It turns on the TV
- For a AC: It turns on the air conditioner
- For Lights: It turns on the lights
Simple Definition: Polymorphism = Same method name, different behavior depending on the object
🔊 The Classic Shape Example
Copy
class Shape:
def area(self):
pass # Each shape calculates differently
def draw(self):
pass # Each shape draws differently
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def draw(self):
print("⭕ Drawing a circle")
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def draw(self):
print("⬜ Drawing a rectangle")
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
def draw(self):
print("🔺 Drawing a triangle")
# THE MAGIC OF POLYMORPHISM ✨
# Same code works for ANY shape!
shapes = [
Circle(5),
Rectangle(4, 6),
Triangle(3, 8)
]
print("Drawing all shapes and calculating areas:\n")
for shape in shapes:
shape.draw() # Same method call
print(f" Area: {shape.area():.2f}\n") # Different results!
Copy
Drawing all shapes and calculating areas:
⭕ Drawing a circle
Area: 78.54
⬜ Drawing a rectangle
Area: 24.00
🔺 Drawing a triangle
Area: 12.00
🎵 Real-World Example: Music Player
Imagine a music player that can play different types of audio:Copy
class AudioFile:
def __init__(self, filename):
self.filename = filename
def play(self):
raise NotImplementedError("Subclass must implement this!")
class MP3File(AudioFile):
def play(self):
print(f"🎵 Playing MP3: {self.filename}")
print(" Using MP3 decoder...")
class WAVFile(AudioFile):
def play(self):
print(f"🎵 Playing WAV: {self.filename}")
print(" Raw audio, high quality!")
class FLACFile(AudioFile):
def play(self):
print(f"🎵 Playing FLAC: {self.filename}")
print(" Lossless compression!")
class OGGFile(AudioFile):
def play(self):
print(f"🎵 Playing OGG: {self.filename}")
print(" Open source format!")
# Music Player doesn't care about file type!
class MusicPlayer:
def __init__(self):
self.playlist = []
def add_to_playlist(self, audio_file):
self.playlist.append(audio_file)
def play_all(self):
print("=" * 40)
print("🎧 Now Playing Your Playlist")
print("=" * 40)
for track in self.playlist:
track.play() # Polymorphism! Same method, different behavior
print()
# Create playlist with different file types
player = MusicPlayer()
player.add_to_playlist(MP3File("song1.mp3"))
player.add_to_playlist(WAVFile("sound_effect.wav"))
player.add_to_playlist(FLACFile("classical.flac"))
player.add_to_playlist(OGGFile("podcast.ogg"))
player.play_all()
🎮 Fun Example: Game Attacks
Every character attacks differently, but the game just callsattack()!
Copy
class Character:
def __init__(self, name, health):
self.name = name
self.health = health
def attack(self, target):
raise NotImplementedError("Each character attacks differently!")
def take_damage(self, amount):
self.health -= amount
status = "💀 Defeated!" if self.health <= 0 else f"❤️ {self.health} HP left"
print(f" {self.name}: {status}")
class Warrior(Character):
def attack(self, target):
print(f"⚔️ {self.name} slashes with sword!")
target.take_damage(25)
class Mage(Character):
def attack(self, target):
print(f"🔥 {self.name} casts Fireball!")
target.take_damage(35)
class Archer(Character):
def attack(self, target):
print(f"🏹 {self.name} shoots an arrow!")
target.take_damage(20)
class Healer(Character):
def attack(self, target):
print(f"✨ {self.name} throws holy light!")
target.take_damage(10)
def heal(self, target):
print(f"💚 {self.name} heals {target.name}!")
target.health += 30
class Ninja(Character):
def attack(self, target):
print(f"🌀 {self.name} appears behind the enemy!")
print(f" Triple strike combo!")
target.take_damage(15)
target.take_damage(15)
target.take_damage(15)
# THE BATTLE!
def battle_round(attackers, defender):
print("\n" + "=" * 50)
print(f"🎯 Target: {defender.name}")
print("=" * 50)
for attacker in attackers:
attacker.attack(defender) # Same call, different attacks!
if defender.health <= 0:
print(f"\n🏆 {defender.name} has been defeated!")
return
# Create team
team = [
Warrior("Conan"),
Mage("Gandalf"),
Archer("Legolas"),
Ninja("Naruto")
]
# Create enemy
dragon = Character("Dragon", 200)
dragon.health = 200
# Battle!
battle_round(team, dragon)
📚 Types of Polymorphism
Method Overriding
Same method name in parent and childChild provides its own version
Copy
class Animal:
def speak(self): pass
class Dog(Animal):
def speak(self):
print("Woof!")
Duck Typing
If it walks like a duck…Python doesn’t check type, just method
Copy
def make_it_speak(thing):
thing.speak() # Works if has speak()
🦆 Duck Typing Explained
“If it walks like a duck and quacks like a duck, it must be a duck!”Python doesn’t care about the TYPE, only about whether the object has the method:
Copy
class Duck:
def quack(self):
print("Quack! 🦆")
def walk(self):
print("Waddle waddle 🚶")
class Person:
def quack(self):
print("I'm pretending to be a duck! 🗣️")
def walk(self):
print("Walking normally 🚶")
class Robot:
def quack(self):
print("QUACK.exe executed 🤖")
def walk(self):
print("*mechanical walking sounds* 🦿")
# This function works with ANYTHING that can quack and walk
def do_duck_things(creature):
creature.quack()
creature.walk()
# All of these work!
print("=== Real Duck ===")
do_duck_things(Duck())
print("\n=== Person ===")
do_duck_things(Person())
print("\n=== Robot ===")
do_duck_things(Robot())
🎨 Practical Example: Drawing Application
Copy
from abc import ABC, abstractmethod
class Drawable(ABC):
"""Anything that can be drawn on screen"""
@abstractmethod
def draw(self):
pass
@abstractmethod
def get_area(self):
pass
class Circle(Drawable):
def __init__(self, x, y, radius, color):
self.x = x
self.y = y
self.radius = radius
self.color = color
def draw(self):
print(f"⭕ Circle at ({self.x}, {self.y})")
print(f" Radius: {self.radius}, Color: {self.color}")
def get_area(self):
return 3.14159 * self.radius ** 2
class Rectangle(Drawable):
def __init__(self, x, y, width, height, color):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def draw(self):
print(f"⬜ Rectangle at ({self.x}, {self.y})")
print(f" Size: {self.width}x{self.height}, Color: {self.color}")
def get_area(self):
return self.width * self.height
class Text(Drawable):
def __init__(self, x, y, content, font_size):
self.x = x
self.y = y
self.content = content
self.font_size = font_size
def draw(self):
print(f"📝 Text at ({self.x}, {self.y})")
print(f" \"{self.content}\" (size: {self.font_size})")
def get_area(self):
# Text area is approximate
return len(self.content) * self.font_size * 0.5
class Canvas:
def __init__(self, name):
self.name = name
self.elements = []
def add(self, drawable):
self.elements.append(drawable)
def render(self):
print(f"\n🖼️ Rendering Canvas: {self.name}")
print("=" * 40)
for element in self.elements:
element.draw() # Polymorphism!
print()
def total_area(self):
return sum(e.get_area() for e in self.elements)
# Create a drawing
canvas = Canvas("My Artwork")
canvas.add(Circle(100, 100, 50, "red"))
canvas.add(Rectangle(200, 50, 80, 60, "blue"))
canvas.add(Text(50, 200, "Hello World!", 24))
canvas.add(Circle(300, 300, 30, "green"))
canvas.render()
print(f"📐 Total covered area: {canvas.total_area():.2f}")
💳 Example: Payment System
Different payment methods, same interface:Copy
class PaymentMethod:
def pay(self, amount):
raise NotImplementedError
def get_name(self):
raise NotImplementedError
class CreditCard(PaymentMethod):
def __init__(self, card_number, cvv):
self.card_number = card_number[-4:] # Only store last 4
def pay(self, amount):
print(f"💳 Credit Card ending in {self.card_number}")
print(f" Processing ${amount:.2f}...")
print(f" ✅ Payment successful!")
return True
def get_name(self):
return f"Card ***{self.card_number}"
class PayPal(PaymentMethod):
def __init__(self, email):
self.email = email
def pay(self, amount):
print(f"🅿️ PayPal ({self.email})")
print(f" Redirecting to PayPal...")
print(f" ✅ ${amount:.2f} paid via PayPal!")
return True
def get_name(self):
return f"PayPal: {self.email}"
class Crypto(PaymentMethod):
def __init__(self, wallet_address):
self.wallet = wallet_address[:8] + "..."
def pay(self, amount):
btc_amount = amount / 50000 # Fake conversion
print(f"₿ Crypto Wallet ({self.wallet})")
print(f" Converting ${amount:.2f} to {btc_amount:.6f} BTC...")
print(f" ⏳ Waiting for blockchain confirmation...")
print(f" ✅ Transaction confirmed!")
return True
def get_name(self):
return f"Crypto: {self.wallet}"
class ApplePay(PaymentMethod):
def __init__(self, device_name):
self.device = device_name
def pay(self, amount):
print(f"🍎 Apple Pay ({self.device})")
print(f" Authenticating with Face ID...")
print(f" ✅ ${amount:.2f} paid!")
return True
def get_name(self):
return f"Apple Pay: {self.device}"
# Checkout system - doesn't care WHICH payment method!
class Checkout:
def __init__(self):
self.cart_total = 0
def process_payment(self, payment_method, amount):
print("\n" + "=" * 50)
print(f"🛒 Checkout - Total: ${amount:.2f}")
print(f"📱 Using: {payment_method.get_name()}")
print("=" * 50)
# POLYMORPHISM! Same call, different behavior
success = payment_method.pay(amount)
if success:
print("\n🎉 Thank you for your purchase!")
return success
# Test different payment methods
checkout = Checkout()
# Same checkout process, different payment methods
checkout.process_payment(CreditCard("1234567890123456", "123"), 99.99)
checkout.process_payment(PayPal("user@email.com"), 49.99)
checkout.process_payment(Crypto("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"), 199.99)
checkout.process_payment(ApplePay("iPhone 15"), 29.99)
✅ Benefits of Polymorphism
| Benefit | Description |
|---|---|
| Flexibility | Add new types without changing existing code |
| Simplicity | One interface for many implementations |
| Maintainability | Changes in one class don’t affect others |
| Testability | Easy to swap implementations for testing |
🧪 Practice Exercise
Challenge: Build a Notification System
Challenge: Build a Notification System
Create a notification system that can send messages through different channels:
- EmailNotification: Sends via email
- SMSNotification: Sends via text message
- PushNotification: Sends to mobile app
- SlackNotification: Sends to Slack channel
send(message) method!Copy
class Notification:
def send(self, message):
raise NotImplementedError
class EmailNotification(Notification):
def __init__(self, email):
# TODO
pass
def send(self, message):
# TODO: Print sending email
pass
# TODO: Add SMS, Push, Slack notifications
# Test with NotificationService
class NotificationService:
def notify_all(self, channels, message):
for channel in channels:
channel.send(message)
# Test
service = NotificationService()
channels = [
EmailNotification("user@example.com"),
SMSNotification("+1234567890"),
PushNotification("user_device_123"),
SlackNotification("#general")
]
service.notify_all(channels, "Your order has shipped!")
🏃 Next Up: Abstraction
Now let’s learn how to hide complex details and show only what’s necessary!Continue to Abstraction →
Learn how to hide complexity and create simple interfaces - like a car dashboard!