Tutorials Logic, IN info@tutorialslogic.com

Polymorphism in Python Duck Typing Overloading

Polymorphism Basics

Polymorphism means different objects can be used through the same operation when they provide the expected behavior.

In Python, the object does not need to inherit from a specific class if it has the method your code calls.

This keeps code flexible: write the loop once, and let each object decide how its own method works.

Python Polymorphism

Polymorphism means "many forms." In Python, it allows different classes to be treated through the same interface - the same method name works differently depending on the object calling it.

Method Polymorphism

Same Method Name on Different Objects

Same Method Name on Different Objects
class Dog:
    def speak(self) -> str:
        return "Woof!"

class Cat:
    def speak(self) -> str:
        return "Meow!"

class Duck:
    def speak(self) -> str:
        return "Quack!"

# Polymorphism - same interface, different behavior
animals = [Dog(), Cat(), Duck()]
for animal in animals:
    print(animal.speak())   # Woof! / Meow! / Quack!

# Works with a function too
def make_sound(animal):
    print(animal.speak())   # doesn't care about the type

make_sound(Dog())   # Woof!
make_sound(Cat())   # Meow!

Duck Typing

Python uses "duck typing" - if an object has the right methods, it works, regardless of its class. "If it walks like a duck and quacks like a duck, it's a duck."

Process Any Object with the Needed Method

Process Any Object with the Needed Method
class TextFile:
    def read(self) -> str:
        return "Reading from text file"

class NetworkStream:
    def read(self) -> str:
        return "Reading from network"

class DatabaseCursor:
    def read(self) -> str:
        return "Reading from database"

# This function works with ANY object that has a read() method
def process(source):
    data = source.read()
    print(f"Got: {data}")

process(TextFile())       # Got: Reading from text file
process(NetworkStream())  # Got: Reading from network
process(DatabaseCursor()) # Got: Reading from database

# Built-in polymorphism
print(len("hello"))    # 5  - works on strings
print(len([1, 2, 3]))  # 3  - works on lists
print(len({"a": 1}))   # 1  - works on dicts

Operator Overloading

Python lets you define how operators like +, -, *, == work on your custom classes using dunder methods.

Add Vector Objects

Add Vector Objects
class Vector:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def __add__(self, other: "Vector") -> "Vector":
        return Vector(self.x + other.x, self.y + other.y)

    def __sub__(self, other: "Vector") -> "Vector":
        return Vector(self.x - other.x, self.y - other.y)

    def __mul__(self, scalar: float) -> "Vector":
        return Vector(self.x * scalar, self.y * scalar)

    def __eq__(self, other: "Vector") -> bool:
        return self.x == other.x and self.y == other.y

    def __abs__(self) -> float:
        import math
        return math.sqrt(self.x**2 + self.y**2)

    def __str__(self) -> str:
        return f"Vector({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)

print(v1 + v2)   # Vector(4, 6)
print(v2 - v1)   # Vector(2, 2)
print(v1 * 3)    # Vector(3, 6)
print(abs(v2))   # 5.0
print(v1 == Vector(1, 2))  # True

Abstract Classes

Use ABC and @abstractmethod to define an interface that subclasses must implement.

Require a Shared Method

Require a Shared Method
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        pass

    @abstractmethod
    def perimeter(self) -> float:
        pass

    def describe(self) -> str:
        return f"Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}"

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

    def area(self) -> float:
        import math
        return math.pi * self.radius ** 2

    def perimeter(self) -> float:
        import math
        return 2 * math.pi * self.radius

class Rectangle(Shape):
    def __init__(self, w: float, h: float):
        self.w = w
        self.h = h

    def area(self) -> float:
        return self.w * self.h

    def perimeter(self) -> float:
        return 2 * (self.w + self.h)

shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
    print(shape.describe())

# Shape()  # TypeError - can't instantiate abstract class
Polymorphism readiness check

Can You Use One Interface for Many Objects?

4 checks
  • The same method call can work on different object types when each object provides that behavior.
  • Duck typing accepts an object because it supplies the required behavior, not because it inherits from one named class.
  • Dunder methods let custom objects respond to operators in a predictable Python style.
  • An ABC can require selected methods when runtime inheritance is part of the design.

Interface Surprises

  • Checking types before trying behavior

    Prefer calling the expected method when different objects share the same operation.
  • Giving methods the same name but different meaning

    Shared method names should represent the same idea across object types.
  • Forgetting return shape

    Keep return values compatible when objects are used through the same operation.

Try this next

Call One Method on Many Objects

0 of 3 completed

  1. Create two classes with speak() and loop through both objects.
  2. Give CSVReport and JsonReport a render() method with compatible returns.
  3. Rewrite one if type block so it calls a shared method instead.

Questions About Polymorphism

No. Python often uses duck typing: if an object has the needed method, the code can use it.

A report exporter can call export() on PDF, CSV, and JSON objects without knowing their internal details.

Use clear method names, tests, and sometimes abstract base classes or protocols for larger projects.

Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.