๐ฆ What is Polymorphism?
The word polymorphism comes from Greek:
Poly = Many
Morph = Forms
So polymorphism means โmany formsโ !
Imagine a universal remote control ๐บ. You press the โONโ button and:
For a TV : It turns on the TV
For a AC : It turns on the air conditioner
For Lights : It turns on the lights
Same button, different results based on what youโre controlling!
Simple Definition : Polymorphism = Same method name, different behavior depending on the object
๐ The Classic Shape Example
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!
Output:
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:
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 calls attack()!
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 child Child provides its own version 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 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:
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
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:
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( "[email protected] " ), 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
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
All should have a send(message) method! 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( "[email protected] " ),
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!