Learn Polymorphism in Python by Example

Published On: Wed, 26 Mar 2025 Updated On: Wed, 26 Mar 2025

Polymorphism means "One with multiple forms". It is one of the key features of object-oriented programming(OOP). It empowers developers to write flexible and adaptable code allowing different objects to respond to the same function or method in distinct ways. Therefore, promoting code reusability and modularity. This article will walk through the concept of Polymorphism in Python. You will understand its significance in OOP, polymorphic functions, method overloading and method overriding, and how inheritance and interfaces enable polymorphic behaviour. Through practical examples, you will witness the true power and versatility of polymorphism.

Understanding the Polymorphism

Imagine you are visiting a grocery store to buy several grocery items. The shop has a shopkeeper who finds and gives the requested item(s). Each time you request him for some item, he will bring the item and hand it over to you. agrocery store with a shopkeeper who is selling the groceries

Here, you need to pay attention to that shop is a complete application and the shopkeeper is the single function that serves you to get the items you want based on what data you pass to the shopkeeper() function. You can not deny the below possibilities:

1. Asking the shopkeeper for each item one by one. See the below sample code.

Code Example
inventory = [
    'apple',
    'oranges',
    'pineapple',
    'avocado',
    'grapes',
    'mango',
    ...
]

def shopkeeper(item):
    if item in inventory:
        return item
    
    return f'{item} - Not available'

print(shopkeeper('grapes'))
print(shopkeeper('potatos'))

# Output
# grapes
# potatos - Not available

2. Ask the shopkeeper to give all items at once. See the code below.

Code Example
inventory = [
    'apple',
    'oranges',
    'pineapple',
    'avocado',
    'grapes',
    'mango',
    ...
]

def shopkeeper(items):
    result = []
    for item in items:
        if item in inventory:
            result.append(item)
        else:
            result.append(f'{item} - Not available')
    
    return result

print(shopkeeper(['grapes', 'potatos', 'oranges']))

As you can understand the shopkeeper assumes the role of multiple nature of work. He can facilitate a single item as well as multiple items based on the request. This is the concept of polymorphism where one function/object changes its behaviour based on the nature of work.

Significance of Polymorphism in OOP

You might know the trigonometry terminology to denote multi-angle shapes. We have different words such as Triangle, Rectangle, Pentagon, Hexagon etc. To denote all these at once we commonly use "Polygon"  meaning the shape has many angles. Below is the Google screenshot for reference of where this "polygon" word came from.Etymology of polygon

The "Polymorphism" word also comes from the Greek words "poly," meaning "many," and "morph," meaning "form". Therefore, we can understand the word "Polymorphic" as "The one who has many shapes" and "Polymorphism" as the "ability to take multiple forms".

So, do you think the shopkeeper() function is polymorphic? If you differ, please comment.

In our shopkeeper example, you must have seen that two functions operate on different data types that are string and list.

In the Object Oriented Programming context, polymorphism allows a single function or method to operate on different data types or classes seamlessly. It weaponizes developers to write code that can handle diverse data without knowing the specific types at compile time. Let's combine both examples for different possibilities below:

Code Example
inventory = [
    'apple',
    'oranges',
    'pineapple',
    'avocado',
    'grapes',
    'mango',
    ...
]

def shopkeeper(item):
    if isinstance(item, str):     # Check if the shopkeeper function is given "string" parameter
        if item in inventory:
            return item
        else:
            return f'{item} - Not available'
    elif isinstance(item, list):  # Check if the shopkeeper function is given "list" parameter
        result = []
        for i in item:
            if i in inventory:
                result.append(i)
            else:
                result.append(f'{i} - Not available')
        return result
    else:
        raise TypeError("Invalid input type")

# Now you can pass either single item or all items to shopkeeper()
print("Ask shopkeeper to give items one by one")
print(shopkeeper('grapes'))
print(shopkeeper('potatos'))
print("Ask shopkeeper to give all items at once")
print(shopkeeper(['grapes', 'potatos', 'oranges']))

Polymorphism promotes the concept of reusability and Do not Repeat Yourself(DRY), making it tremendously easier to maintain and extend applications. Developers can create more flexible and robust software solutions by designing classes and functions with polymorphic capabilities.

Polymorphic Functions

Method Overloading

shopkeeper() is a polymorphic function. Let's take another example so that it becomes more clear to you. Consider a simple addition function:

Code Example
def add(a, b):
    return a + b

This add function can handle various data types, such as integers, floats, and concatenate strings.

Code Example
print(add(2, 3))       # Output: 5
print(add(3.14, 2.86)) # Output: 6.0
print(add("Hello, ", "World!"))  # Output: "Hello, World!"

Method overloading is another way to achieve polymorphism within classes. It allows a class to have multiple methods with the same name but different parameters. Being a dynamic typing language, Python does not natively support method overloading, but we can achieve it through default parameter values or using the *args and **kwargs techniques.

See the below example that demonstrates that the second method overloads the previous method definition even if we pass different data types.

Code Example
class Calculator:
    def add(self, a:int, b:int):
        print("I am adding numeric values")
        return a + b

    def add(self, a:str, b:str):
        print("I am concatanating string values")
        return a + b
    
calc = Calculator()
print(calc.add(2, 3))
print(calc.add("Smiaansh", "Technologies"))

Method Overriding

Polymorphism becomes potent when combined with inheritance. Subclasses can override methods from their superclass(s), providing their implementation while maintaining the same method name. It allows different classes to exhibit polymorphic behaviour while adhering to a standard interface.

Code Example
class Shape:
    def area(self):
        pass  # Abstract method

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius ** 2

In this example, Shape is the superclass, while Rectangle and Circle are subclasses. Both subclasses override the area() method to calculate their specific area formulas.

Comparison Table

Feature Method Overloading Method Overriding
Definition Same method name, different parameters Subclass overrides superclass method
Purpose Handle varied inputs Customize functionality
Python Native Support No Yes

Practical Examples

Polymorphism is evident in many built-in Python functions, such as len(), str(), and sum(). These functions can work with various data types, adjusting their behaviour accordingly.

Code Example
print(len([1, 2, 3]))        # Output: 3
print(len("Hello, World!"))  # Output: 13
print(str(123))              # Output: "123"
print(str(3.14))             # Output: "3.14"
print(sum([1, 2, 3]))        # Output: 6
print(sum((1, 2, 3)))        # Output: 6

Common Mistakes

Here’s a detailed breakdown of common mistakes developers make when working with polymorphism in Python, along with explanations and tips to avoid them:

1. Using Polymorphism Without Understanding Input Types

  • Mistake: Developers often assume that the input types are consistent without explicitly checking. This can lead to errors if the function receives unexpected data.

  • Example:

    Code Example
    def process(item):
        return item.upper()  # Assumes the item is a string
    print(process(["hello"]))  # Fails with AttributeError
  • How to Avoid:

    • Use functions like isinstance() to handle different types or raise appropriate errors.

    • Example Fix:

      Code Example
      def process(item):
          if isinstance(item, str):
              return item.upper()
          else:
              raise TypeError("Expected a string")

2. Overcomplicating Polymorphic Logic

  • Mistake: Writing overly complex polymorphic methods that mix too many responsibilities, making them hard to maintain.

  • Example:

    Code Example
    def process(item):
        if isinstance(item, int):
            return item ** 2
        elif isinstance(item, str):
            return item[::-1]
        elif isinstance(item, list):
            return sum(item)
  • How to Avoid:

    • Stick to the Single Responsibility Principle and split such logic into multiple functions or methods. Keep each function focused on one task.

3. Forgetting to Override Methods Properly

  • Mistake: Subclasses may unintentionally fail to override methods due to typos or method signature mismatches.

  • Example:

    Code Example
    class Parent:
        def greet(self):
            print("Hello!")
    
    class Child(Parent):
        def greeting(self):  # Typo: should be greet
            print("Hi!")
    
    obj = Child()
    obj.greet()  # Calls Parent's method instead of Child's
  • How to Avoid:

    • Double-check overridden method names and parameters.

    • Use abstract base classes (ABC module) to enforce method overriding where necessary.

4. Overriding Without Calling super()

  • Mistake: When overriding a method in a subclass, failing to call the parent class's method can lead to loss of functionality.

  • Example:

    Code Example
    class Animal:
        def sound(self):
            print("Animal makes a sound")
    
    class Dog(Animal):
        def sound(self):
            print("Dog barks")  # Doesn't call the parent method
  • How to Avoid:

    • Use super() to include the parent class behavior:

      Code Example
      class Dog(Animal):
          def sound(self):
              super().sound()
              print("Dog barks")

5. Misusing Method Overloading

  • Mistake: Python doesn’t natively support method overloading (like Java or C++), but developers may try to use it incorrectly.

  • Example:

    Code Example
    class Calculator:
        def add(self, a, b):
            return a + b
    
        def add(self, a, b, c):  # Overwrites the previous method
            return a + b + c
    
    calc = Calculator()
    print(calc.add(2, 3))  # Error: add() only supports three arguments now
  • How to Avoid:

    • Use default arguments or *args for flexibility.

    • Example Fix:

      Code Example
      class Calculator:
          def add(self, *args):
              return sum(args)

6. Confusing Method Overriding and Overloading

  • Mistake: Treating method overriding as method overloading or vice versa, leading to incorrect implementation.

  • Explanation:

    • Overriding occurs when a subclass redefines a method from its parent class.

    • Overloading allows the same method name with different parameters (not natively supported in Python).

  • Tip: Be clear about the differences and apply them appropriately.

7. Ignoring DRY (Don't Repeat Yourself) Principle

  • Mistake: Rewriting similar methods instead of leveraging polymorphism through inheritance or interfaces.

  • Example:

    Code Example
    class Circle:
        def area(self, radius):
            return 3.14 * radius ** 2
    
    class Square:
        def area(self, side):
            return side * side
  • How to Avoid:

    • Use a polymorphic base class:

      Code Example
      class Shape:
          def area(self):
              pass  # Abstract method
      
      class Circle(Shape):
          def area(self, radius):
              return 3.14 * radius ** 2
      
      class Square(Shape):
          def area(self, side):
              return side * side

8. Overlooking Code Readability

  • Mistake: Writing overly compact polymorphic code that sacrifices readability for cleverness.

  • Example:

    Code Example
    def process(data):
        return [i ** 2 if isinstance(i, int) else i[::-1] for i in data]
  • How to Avoid:

    • Break down complex logic into smaller, readable functions or use comments to explain the logic.

9. Ignoring Testing for Polymorphic Scenarios

  • Mistake: Developers often test individual methods but overlook testing their polymorphic behavior with various inputs.

  • How to Avoid:

    • Create unit tests for all possible scenarios:

      Code Example
      def test_shopkeeper():
          assert shopkeeper("grapes") == "grapes"
          assert shopkeeper(["apple", "potato"]) == ["apple", "potato - Not available"]

10. Not Leveraging Built-In Functions

  • Mistake: Rewriting functions that Python already provides polymorphically, like len(), sum(), etc.

  • How to Avoid:

    • Familiarize yourself with Python’s built-in polymorphic functions and reuse them wherever possible.

By addressing these common mistakes, you can write cleaner, more efficient, and error-free polymorphic code in Python. Let me know if you’d like deeper examples for any of these!

Conclusion

Polymorphism is a powerful concept in Python that enhances the flexibility and adaptability of object-oriented programs. Polymorphism promotes code reuse and modularity by allowing functions and methods to operate on different data types and classes. Python empowers developers to write versatile and scalable applications leveraging polimorphism along with function or method overloading & overriding through inheritance. Embrace the essence of polymorphism and elevate your Python programming to new heights of efficiency and elegance.

Learn Polymorphism in Python by Example

Reader Comments


Add a Comment