Python

P
PythonHub
Progress 0%

Python

  • Home
  • History of Python
  • Applications of Python
  • Introduction To Python
    • What is Python
    • Character Set
    • Tokens in Python
    • Python Execution Mode
    • Variable And Identifiers
    • Data Types in Python
    • Operators And Expressions
    • Constants in Python
    • Assignment Statement
    • Input / Output in Python
    • Simple Python Scripts
    • Namespace in Python
    • šŸ“ Assignments
  • Operators in Python
    • Arithmetic Operators
    • Assignment Operators
    • Shorthand Assignment Operators
    • Relational Operators
    • Logical Operators
    • Bitwise Operators
    • Special Operators
    • šŸ“ Assignments
  • Input Output in Python
    • Accept Input
    • Output Formatting
    • šŸ“ Assignments
  • Conditional Statement
    • Decision Making
    • if Statement
    • IF-ELSE STATEMENT
    • IF-ELSE LADDER
    • NESTED IF-ELSE
    • Short Hand IF-ELSE
    • šŸ“ Assignments
  • Loops
    • Introduction to Loops
    • While Loop
    • Nested While Loop
    • šŸ“ While Loop Assignments
    • For Loop
    • For Loop Examples
    • Nested For Loop
    • Nested For Loop Examples
    • Infinite While Loops
    • Infinite For Loops
    • Break, Continue and Else in Loops
    • Difference Between For and While Loop
    • For Each Loop
    • šŸ“ For Each Assignments
    • šŸ“ All Loops Assignments
  • List
    • List in Python
    • Access List Elements
    • List Functions
    • Iterate (Loop) List
    • List Comprehension
    • šŸ“ Assignments
  • Tuple
    • Tuple in Python
    • Access Tuple Elements
    • Tuple Functions
    • Iterate (Loop) Tuple
    • Unpack Tuple
    • Tuple Comprehension
    • šŸ“ Assignments
  • Set
    • Set in Python
    • Access Set Elements
    • Set Methods
    • Iterate (Loop) Set
    • Pack/Unpack Set
    • Set Comprehension
    • šŸ“ Assignments
  • Dictionary
    • Dictionary
    • Access Dictionary Items
    • Dictionary Methods
    • Iterate (Loop) Dictionary
    • Formatting Dictionaries
    • Nested Dictionaries
    • Dictionary Comprehension
    • šŸ“ Assignments
  • Diff List Tuple Set Dictionary
    • List vs Tuple
    • List vs Set
    • List vs Dictionary
    • Tuple vs Set
    • Tuple vs Dictionary
    • Dictionary vs Set
    • šŸ“ Assignments
  • Exception
    • Error vs Exception
    • Types of Exception
    • Exception Handling
    • User Defined Exception
    • Logging Exception
    • šŸ“ Assignments
  • Functions
    • Introduction to Functions
    • Modular Programming
    • Types of Functions
    • Inbuilt Functions
    • Need For User-Defined Function
    • Elements of User Defined Function
    • Function Arguments
    • Nesting of Functions
    • Recursion
    • Global Local and Non Local
    • Python Lambda Functions
    • šŸ“ Assignments
  • Python Module
    • Introduction to Module
    • Inbuilt Modules in Python
    • User Defined Module
    • šŸ“ Assignments
  • File Handling
    • Introduction to Files
    • Create File
    • Read Files
    • Write to File
    • Rename File
    • Copy File
    • Move File
    • List Files in Directory
    • Binary Files
    • Zipping and Unzipping Files
    • šŸ“ Assignments
  • Strings
    • Basics of Strings
    • String Special Operators
    • String Formatting Operators
    • String Methods
    • šŸ“ Assignments
  • Regular Expressions
  • Python OOPS
    • Basics of Object Oriented
    • What are Classes and Objects?
    • Creating Class and Object
    • OOP vs Procedural Programming
    • Difference Between Classes and Objects
    • Constructors
    • Destructor
    • Built Class Methods and Attributes
    • Class and Instance Variables
    • Inheritance in Python
    • Single Inheritance
    • Multiple Inheritance
    • Multilevel Inheritance
    • Hierarchical Inheritance
    • Hybrid Inheritance
    • Abstraction
    • Method Overriding
    • Abstract Method
    • Interfaces in Python
    • Abstract Class vs Interface
    • Public, Private and Protected
    • Overloading vs Overriding
    • Inheritance vs Composition
    • Encapsulation
    • Polymorphism
    • Inner Classes
    • šŸ“ Assignments
  • Advanced Python
    • Iterator in Python
    • Generator in Python
    • Decorator in Python
    • Type Hints in Python
    • Async/Await Programming
    • Dataclasses in Python
    • Context Managers in Python
    • Property Decorator in Python
    • __slots__ in Python
    • Enums in Python
    • Walrus Operator in Python
    • Match-Case in Python
    • šŸ“ Assignments
  • Python Standard Library
    • Collections Module
    • Itertools Module
    • Functools Module
    • Datetime Module
    • JSON Module
    • OS Module
    • Sys Module
    • Random Module
    • Math Module
    • šŸ“ Assignments
  • Python Testing
    • Unit Testing in Python
    • Pytest Framework
    • Mocking in Python
    • šŸ“ Assignments
  • Python Best Practices
    • PEP 8 Style Guide
    • Docstrings in Python
    • Logging in Python
    • Code Optimization Tips
    • Debugging Techniques
    • šŸ“ Assignments
  • MySQL Database in Python
    • Introduction to MySQL with Python
    • DBMS vs File System
    • Connecting to MySQL Database
    • Create Database in MySQL
    • Create Table in MySQL
    • Insert Data in MySQL
    • Insert Multiple Rows
    • Select Data from MySQL
    • WHERE Clause in MySQL
    • Update Data in MySQL
    • Delete Data from MySQL
    • Parameterized Queries
    • Transaction Management
    • Error Handling
    • Connection Pooling
    • MySQL Drivers Guide
    • Joins in MySQL
    • Aggregation Functions
    • Backup MySQL Database
    • Best Practices
    • šŸ“ Assignments
  • MySQL Database Operations
    • SELECT Statement
    • MySQL Operators
    • DDL Statements
    • DML Statements
    • Subqueries
    • JOIN Operations
    • Aggregation
    • Case Study
    • šŸ“ Assignments
  • Graphics in Python
  • Threads in Python
    • Introduction to Threads
    • Process vs Threads
    • Concurrent Programming & GIL
    • Uses of Threads
    • Creating Threads
    • Single Tasking
    • Multi Tasking
    • Thread Synchronization
    • šŸ“ Assignments
  • Interview Questions & Answers
  • Python Case Studies
  • Multiple Choice Questions
  • šŸ“ Practice Problems
Get in Touch
  • tech2dsm@gmail.com

© Sankalan Data Tech

Python Language Interactive Tutorial

Python: Polymorphism

Python Polymorphism - Complete Guide

Learn how polymorphism makes your code flexible and reusable.

Created by Sankalan Data Tech Team Verified
Data Engineers, Analysts, Scientists & Trainers
Created by experienced Python developers, data engineers, and data scientists to make programming easy through practical examples, real-world experience, and clear explanations.
On this page:
  • What is Polymorphism?
  • Method Overriding
  • Duck Typing
  • Operator Overloading
  • Real-World Example
  • Best Practices
  • Try It Yourself
  • Quick Quiz
  • Frequently Asked Questions
Share this tutorial:
Twitter LinkedIn Facebook WhatsApp Reddit Telegram Email Copy Link
What You'll Learn Here
  • What is polymorphism — the ability of different objects to respond to the same method call in their own way
  • Method overriding — child classes providing their own implementation
  • Duck typing — Python's approach to polymorphism without inheritance
  • Operator overloading — making operators work with your custom classes
  • Real-world use — practical examples you can use in your projects

What is Polymorphism?

The word polymorphism comes from Greek words meaning "many forms." In programming, it means that the same method name can behave differently depending on the object it's called on.

Think of a play() method. If you call it on a MusicPlayer object, it plays a song. If you call it on a VideoPlayer object, it plays a video. Same method name, different behavior.

Python supports polymorphism in three main ways:

  • Method Overriding — child classes override parent methods
  • Duck Typing — "if it walks like a duck and quacks like a duck..."
  • Operator Overloading — operators like + and * work differently for different types

šŸ’” Key concept: Polymorphism lets you write code that works with objects you haven't even created yet. You just need to know what methods they should have.

Method Overriding

1

Different Objects, Same Method, Different Behavior

Method overriding is the most common way polymorphism works in Python. When a child class defines a method that already exists in the parent class, it "overrides" the parent's version.

This means you can call the same method on different objects and get different results. This is what makes polymorphism powerful.

# Method Overriding — The Foundation of Polymorphism

class Animal:
    """Base class for all animals"""
    
    def __init__(self, name):
        self.name = name
    
    def make_sound(self):
        """Default sound — to be overridden"""
        return "Some animal sound"
    
    def move(self):
        """Default movement — to be overridden"""
        return "Moves somehow"

class Dog(Animal):
    """Dog — overrides make_sound and move"""
    
    def make_sound(self):
        return f"{self.name} says: Woof! Woof!"
    
    def move(self):
        return f"{self.name} runs on four legs"

class Cat(Animal):
    """Cat — overrides make_sound and move"""
    
    def make_sound(self):
        return f"{self.name} says: Meow!"
    
    def move(self):
        return f"{self.name} walks silently"

class Bird(Animal):
    """Bird — overrides make_sound and move"""
    
    def make_sound(self):
        return f"{self.name} says: Chirp! Chirp!"
    
    def move(self):
        return f"{self.name} flies through the air"

# ============================================================
# The Power of Polymorphism
# ============================================================

print("=" * 50)
print("METHOD OVERRIDING — POLYMORPHISM")
print("=" * 50)

# Create different animals
animals = [
    Dog("Rex"),
    Cat("Whiskers"),
    Bird("Tweety")
]

# Same method call — different behavior
print("\nCalling make_sound() on each animal:")
for animal in animals:
    print(f"  {animal.make_sound()}")

print("\nCalling move() on each animal:")
for animal in animals:
    print(f"  {animal.move()}")

# ============================================================
# Polymorphism in Action
# ============================================================

print("\n" + "-" * 30)
print("POLYMORPHISM IN ACTION")
print("-" * 30)

def animal_parade(animals):
    """This function works with ANY animal — past, present, future!"""
    print("šŸŽµ Animal Parade!")
    for animal in animals:
        print(f"  {animal.name}: {animal.move()} — {animal.make_sound()}")

animal_parade(animals)

# ============================================================
# Why This Matters
# ============================================================

print("\n" + "-" * 30)
print("WHY POLYMORPHISM MATTERS")
print("-" * 30)
print("""
1. You can write functions that work with ANY animal class
2. You don't need to know what type of animal you'll get
3. New animals can be added without changing existing code
4. The code is more flexible and easier to maintain

The key insight: The animal_parade() function doesn't care
what type of animal it's working with. It just calls
make_sound() and move() and trusts each object to know
how to handle it.
""")

print("āœ… This is polymorphism — different objects responding")
print("   to the same method call in their own way.")

Method overriding key points:

  • Same method name — different behavior based on the object
  • Child classes provide their own implementation — of inherited methods
  • Works with any subclass — even ones not yet created
  • Makes code flexible — you can add new classes without changing existing code

Quick Check: What makes method overriding polymorphic? (Answer: The same method call produces different behavior depending on the object's type)

Duck Typing

2

If It Walks Like a Duck...

Duck typing is Python's informal approach to polymorphism. The idea is: "If it walks like a duck and quacks like a duck, then it's a duck."

In practice, this means you don't care about the type of an object. You only care about the methods it has. If an object has a quack() method, you can call it — regardless of what class it belongs to.

This is different from method overriding because duck typing doesn't require inheritance. Any class can participate as long as it has the right methods.

# Duck Typing — Polymorphism Without Inheritance

print("=" * 50)
print("DUCK TYPING — POLYMORPHISM WITHOUT INHERITANCE")
print("=" * 50)

# These classes DON'T inherit from a common parent

class Duck:
    def quack(self):
        return "Quack! Quack!"
    
    def fly(self):
        return "Duck flies low"

class Dog:
    def quack(self):
        return "Woof! (I'm trying to quack like a duck!)"
    
    def fly(self):
        return "Dog runs (can't fly)"

class Robot:
    def quack(self):
        return "Beep! Boop! (Quack mode activated)"
    
    def fly(self):
        return "Robot hovers with propellers"

class Car:
    def honk(self):
        return "Beep! Beep!"
    
    # No quack() method!

# ============================================================
# Duck Typing in Action
# ============================================================

def make_it_quack(thing):
    """
    This function doesn't care what 'thing' is.
    It just needs it to have a quack() method.
    """
    return thing.quack()

def make_it_fly(thing):
    """
    This function doesn't care what 'thing' is.
    It just needs it to have a fly() method.
    """
    return thing.fly()

# All these work because all have quack()
print("šŸ¦† Making things quack:")
print(f"  Duck: {make_it_quack(Duck())}")
print(f"  Dog: {make_it_quack(Dog())}")
print(f"  Robot: {make_it_quack(Robot())}")

print("\nāœˆļø Making things fly:")
print(f"  Duck: {make_it_fly(Duck())}")
print(f"  Dog: {make_it_fly(Dog())}")
print(f"  Robot: {make_it_fly(Robot())}")

# This would fail — Car doesn't have quack()
# print(make_it_quack(Car()))  # AttributeError!

# ============================================================
# The Flexibility of Duck Typing
# ============================================================

print("\n" + "-" * 30)
print("THE POWER OF DUCK TYPING")
print("-" * 30)

def describe(thing):
    """
    This function works with ANY object that has the right methods.
    It doesn't care about inheritance or type.
    """
    description = []
    
    # Check if it can do certain things
    if hasattr(thing, 'quack'):
        description.append(f"Can quack: {thing.quack()}")
    
    if hasattr(thing, 'fly'):
        description.append(f"Can fly: {thing.fly()}")
    
    if hasattr(thing, 'honk'):
        description.append(f"Can honk: {thing.honk()}")
    
    return ", ".join(description) if description else "Nothing special"

print("Describing different objects:")
print(f"  Duck: {describe(Duck())}")
print(f"  Dog: {describe(Dog())}")
print(f"  Robot: {describe(Robot())}")
print(f"  Car: {describe(Car())}")

print("\n" + "-" * 30)
print("KEY INSIGHT:")
print("-" * 30)
print("āœ… Duck typing focuses on WHAT an object can do, not WHAT it is.")
print("āœ… No inheritance is required — any class can participate.")
print("āœ… This is extremely flexible and Pythonic.")
print("āš ļø The trade-off: errors are caught at runtime, not compile time.")

Duck typing key points:

  • No inheritance needed — any class can participate
  • Focus on behavior — what methods does the object have?
  • Very flexible — you can use any object that has the right methods
  • Runtime checking — errors are caught when you try to call a missing method
  • Pythonic — "it's easier to ask for forgiveness than permission"

Quick Check: What's the difference between duck typing and method overriding? (Answer: Duck typing doesn't require inheritance; any class with the right methods works)

Operator Overloading

3

Making Operators Work for Your Classes

Operator overloading is another form of polymorphism. It lets you define how operators like +, -, *, and == work with your custom classes.

Think about the + operator. It works differently with numbers (addition) vs strings (concatenation). That's polymorphism in action. You can create your own classes that respond to + in their own way.

# Operator Overloading — Polymorphism with Operators

print("=" * 50)
print("OPERATOR OVERLOADING")
print("=" * 50)

# ============================================================
# Example 1: Vector Class
# ============================================================

class Vector:
    """A 2D vector that supports mathematical operations"""
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        """Overload + : vector addition"""
        if isinstance(other, Vector):
            return Vector(self.x + other.x, self.y + other.y)
        return Vector(self.x + other, self.y + other)
    
    def __sub__(self, other):
        """Overload - : vector subtraction"""
        if isinstance(other, Vector):
            return Vector(self.x - other.x, self.y - other.y)
        return Vector(self.x - other, self.y - other)
    
    def __mul__(self, other):
        """Overload * : scalar multiplication"""
        if isinstance(other, (int, float)):
            return Vector(self.x * other, self.y * other)
        return Vector(self.x * other.x, self.y * other.y)
    
    def __eq__(self, other):
        """Overload == : equality check"""
        if isinstance(other, Vector):
            return self.x == other.x and self.y == other.y
        return False
    
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

# Using the overloaded operators
v1 = Vector(3, 4)
v2 = Vector(1, 2)

print(f"v1 = {v1}")
print(f"v2 = {v2}")
print(f"v1 + v2 = {v1 + v2}")
print(f"v1 - v2 = {v1 - v2}")
print(f"v1 * 3 = {v1 * 3}")
print(f"v1 == v2? {v1 == v2}")
print(f"v1 == Vector(3, 4)? {v1 == Vector(3, 4)}")

# ============================================================
# Example 2: Money Class
# ============================================================

class Money:
    """Money class with currency support"""
    
    def __init__(self, amount, currency="USD"):
        self.amount = amount
        self.currency = currency
    
    def __add__(self, other):
        """Add money (same currency required)"""
        if isinstance(other, Money):
            if self.currency != other.currency:
                raise ValueError(f"Cannot add {self.currency} and {other.currency}")
            return Money(self.amount + other.amount, self.currency)
        return Money(self.amount + other, self.currency)
    
    def __sub__(self, other):
        """Subtract money (same currency required)"""
        if isinstance(other, Money):
            if self.currency != other.currency:
                raise ValueError(f"Cannot subtract {self.currency} and {other.currency}")
            return Money(self.amount - other.amount, self.currency)
        return Money(self.amount - other, self.currency)
    
    def __mul__(self, factor):
        """Multiply money by a number"""
        return Money(self.amount * factor, self.currency)
    
    def __gt__(self, other):
        """Greater than comparison"""
        if isinstance(other, Money):
            if self.currency != other.currency:
                raise ValueError(f"Cannot compare {self.currency} and {other.currency}")
            return self.amount > other.amount
        return self.amount > other
    
    def __repr__(self):
        return f"${self.amount:.2f} {self.currency}"

# Using money operations
m1 = Money(100, "USD")
m2 = Money(50, "USD")

print(f"\n{'-' * 30}")
print("MONEY OPERATIONS")
print("-" * 30)
print(f"m1 = {m1}")
print(f"m2 = {m2}")
print(f"m1 + m2 = {m1 + m2}")
print(f"m1 - m2 = {m1 - m2}")
print(f"m1 * 3 = {m1 * 3}")
print(f"m1 > m2? {m1 > m2}")
print(f"m2 > m1? {m2 > m1}")

# ============================================================
# Common Operator Methods
# ============================================================

print("\n" + "-" * 30)
print("COMMON OPERATOR METHODS")
print("-" * 30)
print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Operator        │ Special Method                             │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ +               │ __add__(self, other)                       │
│ -               │ __sub__(self, other)                       │
│ *               │ __mul__(self, other)                       │
│ /               │ __truediv__(self, other)                   │
│ //              │ __floordiv__(self, other)                  │
│ %               │ __mod__(self, other)                       │
│ **              │ __pow__(self, other)                       │
│ <               │ __lt__(self, other)                        │
│ <=              │ __le__(self, other)                        │
│ ==              │ __eq__(self, other)                        │
│ !=              │ __ne__(self, other)                        │
│ >               │ __gt__(self, other)                        │
│ >=              │ __ge__(self, other)                        │
│ len()           │ __len__(self)                              │
│ str()           │ __str__(self)                              │
│ repr()          │ __repr__(self)                             │
│ []              │ __getitem__(self, key)                     │
│ in              │ __contains__(self, item)                   │
│ with            │ __enter__(self) and __exit__(self, ...)    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
""")

print("āœ… Operator overloading makes your classes feel like built-in types.")
print("āœ… It's a powerful form of polymorphism that makes code more intuitive.")

Operator overloading key points:

  • Special methods — double underscore methods like __add__, __eq__
  • Makes classes intuitive — users can use familiar operators
  • Polymorphism in action — operators mean different things in different contexts
  • Flexible — you can define any behavior you want

Quick Check: What special method is used to overload the + operator? (Answer: __add__)

Real-World Example

4

Building a Shape System

# Real-World Example: Shape System with Polymorphism

import math

print("=" * 60)
print("SHAPE SYSTEM — POLYMORPHISM IN ACTION")
print("=" * 60)

# ============================================================
# BASE CLASS — Shape
# ============================================================

class Shape:
    """Base class for all shapes"""
    
    def __init__(self, name):
        self.name = name
    
    def area(self):
        """Calculate area — to be overridden"""
        return 0
    
    def perimeter(self):
        """Calculate perimeter — to be overridden"""
        return 0
    
    def describe(self):
        """Describe the shape"""
        return f"Shape: {self.name}, Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}"

# ============================================================
# CHILD CLASSES — Override area() and perimeter()
# ============================================================

class Rectangle(Shape):
    def __init__(self, width, height):
        super().__init__("Rectangle")
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def perimeter(self):
        return 2 * (self.width + self.height)

class Circle(Shape):
    def __init__(self, radius):
        super().__init__("Circle")
        self.radius = radius
    
    def area(self):
        return math.pi * self.radius ** 2
    
    def perimeter(self):
        return 2 * math.pi * self.radius

class Triangle(Shape):
    def __init__(self, a, b, c):
        super().__init__("Triangle")
        self.a = a
        self.b = b
        self.c = c
    
    def area(self):
        # Heron's formula
        s = (self.a + self.b + self.c) / 2
        return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
    
    def perimeter(self):
        return self.a + self.b + self.c

class Square(Rectangle):
    def __init__(self, side):
        super().__init__(side, side)
        self.name = "Square"

# ============================================================
# POLYMORPHIC FUNCTIONS
# ============================================================

def print_shape_info(shape):
    """Works with ANY shape — polymorphism in action"""
    print(f"  {shape.describe()}")

def compare_shapes(shape1, shape2):
    """Compare two shapes by area"""
    if shape1.area() > shape2.area():
        return f"{shape1.name} is bigger than {shape2.name}"
    elif shape1.area() < shape2.area():
        return f"{shape2.name} is bigger than {shape1.name}"
    else:
        return f"{shape1.name} and {shape2.name} have the same area"

def total_area(shapes):
    """Calculate total area of any list of shapes"""
    return sum(shape.area() for shape in shapes)

# ============================================================
# DEMONSTRATION
# ============================================================

# Create shapes
shapes = [
    Rectangle(5, 3),
    Circle(4),
    Triangle(3, 4, 5),
    Square(6)
]

print("\n1. SHAPE DESCRIPTIONS")
for shape in shapes:
    print_shape_info(shape)

print("\n2. COMPARING SHAPES")
print(f"  {compare_shapes(shapes[0], shapes[1])}")
print(f"  {compare_shapes(shapes[1], shapes[2])}")
print(f"  {compare_shapes(shapes[2], shapes[3])}")

print(f"\n3. TOTAL AREA")
print(f"  Total area of all shapes: {total_area(shapes):.2f}")

# ============================================================
# DUCK TYPING EXAMPLE — Works with any object that has area()
# ============================================================

print("\n4. DUCK TYPING — Any object with area() works")

class RandomShape:
    """Not related to Shape class, but has area()"""
    def area(self):
        return 42
    
    def name(self):
        return "Random"

def process_shape(shape):
    """Works with ANY object that has area()"""
    if hasattr(shape, 'area'):
        return f"Processing: area = {shape.area()}"
    return "This object doesn't have an area"

print(f"  {process_shape(RandomShape())}")
print(f"  {process_shape(Circle(5))}")

# ============================================================
# POLYMORPHISM WITH BUILT-IN TYPES
# ============================================================

print("\n5. POLYMORPHISM WITH BUILT-IN TYPES")
print(f"  len('hello'): {len('hello')}")  # String
print(f"  len([1, 2, 3]): {len([1, 2, 3])}")  # List
print(f"  len({1, 2, 3, 4}): {len({1, 2, 3, 4})}")  # Set

print("\n" + "=" * 60)
print("KEY TAKEAWAYS:")
print("=" * 60)
print("āœ… Method overriding: Each shape has its own area() and perimeter()")
print("āœ… Duck typing: RandomShape works even without inheritance")
print("āœ… Operator overloading: Built-in types use polymorphism with len()")
print("āœ… The code is flexible and can work with any shape, now or in the future")

Real-world example key points:

  • Method overriding — each shape implements area() and perimeter() differently
  • Duck typing — RandomShape works with process_shape() without inheritance
  • Built-in polymorphism — len() works with strings, lists, sets, and more
  • Flexible functions — total_area() and compare_shapes() work with any shape

Quick Check: What makes the total_area() function polymorphic? (Answer: It works with any object that has an area() method, regardless of type)

Best Practices

5

Using Polymorphism Effectively

# Best Practices for Polymorphism

print("=" * 60)
print("BEST PRACTICES FOR POLYMORPHISM")
print("=" * 60)

# ============================================================
# 1. DESIGN FOR POLYMORPHISM FROM THE START
# ============================================================

print("\n1. DESIGN FOR POLYMORPHISM")

# āœ… DO: Design a clean interface that all subclasses will implement
class DataProcessor:
    """Abstract interface for data processing"""
    def process(self, data):
        raise NotImplementedError("Subclasses must implement process()")
    
    def validate(self, data):
        """Optional: provide a default implementation"""
        return True

class CSVProcessor(DataProcessor):
    def process(self, data):
        return f"Processing CSV: {data}"
    
    def validate(self, data):
        return ".csv" in data

class JSONProcessor(DataProcessor):
    def process(self, data):
        return f"Processing JSON: {data}"
    
    def validate(self, data):
        return ".json" in data

def handle_data(processor, data):
    """Works with ANY processor"""
    if processor.validate(data):
        return processor.process(data)
    return "Invalid data format"

print(f"  CSV: {handle_data(CSVProcessor(), 'data.csv')}")
print(f"  JSON: {handle_data(JSONProcessor(), 'data.json')}")


# ============================================================
# 2. USE DUCK TYPING WISELY
# ============================================================

print("\n2. USE DUCK TYPING WISELY")

# āœ… DO: Use duck typing when behavior is more important than type
def describe_item(item):
    """Works with anything that has these methods"""
    result = []
    if hasattr(item, 'name'):
        result.append(f"Name: {item.name}")
    if hasattr(item, 'size'):
        result.append(f"Size: {item.size}")
    if hasattr(item, 'color'):
        result.append(f"Color: {item.color}")
    return ", ".join(result) if result else "No description available"

class Book:
    name = "Python Book"
    size = "Large"
    color = "Blue"

class Pen:
    name = "Fountain Pen"
    color = "Black"

print(f"  Book: {describe_item(Book())}")
print(f"  Pen: {describe_item(Pen())}")

# āŒ DON'T: Use type checking when duck typing would work
class BadExample:
    def process(self, data):
        # Bad: checking the type directly
        if isinstance(data, str):
            return data.upper()
        elif isinstance(data, list):
            return [item.upper() for item in data]
        else:
            return str(data)

# This is less flexible and less Pythonic


# ============================================================
# 3. USE ABCs FOR FORMAL INTERFACES
# ============================================================

print("\n3. USE ABCs FOR FORMAL INTERFACES")

from abc import ABC, abstractmethod

class Renderable(ABC):
    """Formal interface for renderable objects"""
    
    @abstractmethod
    def render(self):
        pass

class Image(Renderable):
    def render(self):
        return "Rendering image"

class Video(Renderable):
    def render(self):
        return "Rendering video"

def render_media(media):
    """Works with any Renderable"""
    return media.render()

print(f"  Image: {render_media(Image())}")
print(f"  Video: {render_media(Video())}")


# ============================================================
# 4. BE CONSISTENT WITH SPECIAL METHODS
# ============================================================

print("\n4. BE CONSISTENT WITH SPECIAL METHODS")

# āœ… DO: Make operator overloading consistent
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius
    
    def __add__(self, other):
        if isinstance(other, Temperature):
            return Temperature(self.celsius + other.celsius)
        return Temperature(self.celsius + other)
    
    def __sub__(self, other):
        if isinstance(other, Temperature):
            return Temperature(self.celsius - other.celsius)
        return Temperature(self.celsius - other)
    
    def __eq__(self, other):
        if isinstance(other, Temperature):
            return self.celsius == other.celsius
        return False
    
    def __repr__(self):
        return f"{self.celsius}°C"

t1 = Temperature(25)
t2 = Temperature(10)
print(f"  t1: {t1}, t2: {t2}")
print(f"  t1 + t2 = {t1 + t2}")
print(f"  t1 - t2 = {t1 - t2}")
print(f"  t1 == t2? {t1 == t2}")


# ============================================================
# 5. SUMMARY
# ============================================================

print("\n" + "=" * 60)
print("POLYMORPHISM BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ PRACTICE            │ WHY IT MATTERS                            │
ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤
│ Design for it early │ Makes your code extensible from the start  │
│ Use duck typing     │ More flexible, more Pythonic               │
│ Use ABCs for formal │ Clear contracts, better documentation      │
│ Be consistent       │ Users expect intuitive behavior            │
│ Document interfaces │ Others know what to implement              │
│ Keep it simple      │ Don't overcomplicate                      │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

šŸ“Œ REMEMBER:
   • Polymorphism makes your code more flexible
   • It allows you to extend functionality without changing existing code
   • Python supports polymorphism in multiple ways
   • "It's easier to ask for forgiveness than permission"
""")

Best practices summary:

  • Design for polymorphism — plan your interfaces from the start
  • Use duck typing wisely — it's flexible but can lead to runtime errors
  • Use ABCs for formal contracts — when you need to enforce implementation
  • Be consistent — operator overloading should behave as users expect
  • Document your interfaces — tell others what methods to implement

Quick Check: When should you use ABCs vs duck typing? (Answer: ABCs when you want to enforce a contract and document the interface; duck typing when flexibility is more important)

Try It Yourself

Experiment with polymorphism in the editor below.

Loading Pyodide... 0%
Python Code Editor
==================================================
POLYMORPHISM - PRACTICE
==================================================

1. METHOD OVERRIDING
Toyota: Engine starts with key!
Harley: Kick start, engine roars!
Eco: Silent motor starts!

2. DUCK TYPING
Name: Musician | Can play: Playing music
Name: Programmer | Can code: Writing code
Name: Chef | Can cook: Cooking food

3. OPERATOR OVERLOADING
p1 = Point(2, 3), p2 = Point(4, 5)
p1 + p2 = Point(6, 8)
p1 - p2 = Point(-2, -2)
p1 * 3 = Point(6, 9)
p1 == p2? False

==================================================
SUMMARY
šŸ†

You've Got It!

You now understand polymorphism in Python. You know how to use method overriding, duck typing, and operator overloading to make your code more flexible and reusable.

Quick Quiz

Test what you've learned:

1. What is polymorphism in Python?
2. Which approach to polymorphism doesn't require inheritance?
3. What special method is used to overload the + operator?
4. What is duck typing?
5. Which of the following is NOT a form of polymorphism in Python?

Frequently Asked Questions

What is polymorphism in Python? ā–¼

Polymorphism is the ability of different objects to respond to the same method call in their own way. In Python, it's implemented through method overriding, duck typing, and operator overloading.

What's the difference between polymorphism and inheritance? ā–¼

Inheritance is about sharing code between classes. Polymorphism is about different objects responding to the same method call. While method overriding (a form of polymorphism) requires inheritance, duck typing (another form) does not.

Is operator overloading considered polymorphism? ā–¼

Yes! Operator overloading is a form of polymorphism because the same operator (like +) can mean different things depending on the objects it's used with. For example, + adds numbers but concatenates strings.

When should I use duck typing vs inheritance-based polymorphism? ā–¼

Use duck typing when you want maximum flexibility and don't need to enforce a specific interface. Use inheritance-based polymorphism (method overriding) when you want to share code and enforce a contract using ABCs.

Is polymorphism the same as overloading? ā–¼

No. Polymorphism is about different objects responding to the same method call. Overloading is about multiple methods with the same name but different parameters in the same class. Overloading is one way to achieve polymorphic behavior, but they're not the same thing.

How does polymorphism make code better? ā–¼

Polymorphism makes code more flexible, extensible, and maintainable. You can write functions that work with any object that has the right methods, without knowing the specific type. This makes it easy to add new classes without changing existing code.

Where to Go From Here

Now that you understand polymorphism in Python, check out these related topics:

Method Overriding

Learn more about overriding methods in Python.

Learn More →

Inheritance in Python

Understand how inheritance enables polymorphism.

Learn More →

Encapsulation

Learn how encapsulation works with polymorphism.

Learn More →
Interview Resources
  • Python Syntax & Variables Interview Questions
  • Top SQL Interview Questions & Answers
  • SQL Joins: Displaying Data from Multiple Tables FAQ
  • Python Lists and Dictionaries Interview Questions
  • Python OOP Interview Questions
  • SQL Set Operators Interview Questions
Previous: Encapsulation Next: Inner Classes →