6.7 Polymorphism and Duck Typing in Python

Polymorphism is a key principle in Object-Oriented Programming (OOP) that allows objects of different classes to be treated as objects of a common superclass. Polymorphism enables different classes to provide a unique implementation of methods that share the same name. In Python, Duck Typing is a specific form of polymorphism, where the type of an object is determined by its behavior (methods and properties) rather than its explicit class.

In this section, we will explore what polymorphism is, how it works in Python, and how Duck Typing plays an essential role in Python’s dynamic nature.


6.7.1 What is Polymorphism?

Polymorphism allows objects of different types to be treated uniformly based on a shared interface or method signature. It refers to the ability of different objects to respond to the same method call in a way that is appropriate for their specific class. This makes code more flexible and reusable, as you can write functions or methods that can work with different types of objects.

Key Points:

  • Polymorphism allows different objects to provide different behaviors for the same method.
  • It enables writing generic code that can work with objects from different classes as long as they share common methods or attributes.

6.7.2 Method Polymorphism in Python

In Python, polymorphism is typically achieved by defining methods with the same name in different classes. The exact behavior depends on the class of the object that is calling the method. Python’s dynamic typing system makes it easy to implement polymorphism without requiring explicit interfaces or type declarations.

Example:

class Dog:
    def speak(self):
        return "Woof!"

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

class Cow:
    def speak(self):
        return "Moo!"

# Polymorphic behavior
animals = [Dog(), Cat(), Cow()]

for animal in animals:
    print(animal.speak())

Output:

Woof!
Meow!
Moo!

In this example:

  • Each class (Dog, Cat, Cow) defines a method speak() that behaves differently depending on the type of the object.
  • The speak() method is called polymorphically on each object in the animals list, resulting in the correct output for each type of animal.

6.7.3 Polymorphism with Inheritance

Polymorphism also works naturally with inheritance. A parent class can define a method that is overridden by child classes, and when the method is called on a child class object, the child class's version is executed. This allows for more flexible and reusable code.

Example:

class Animal:
    def speak(self):
        raise NotImplementedError("Subclass must implement abstract method")

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# Polymorphic behavior with inheritance
def make_animal_speak(animal):
    print(animal.speak())

# Using polymorphism
dog = Dog()
cat = Cat()

make_animal_speak(dog)  # Output: Woof!
make_animal_speak(cat)  # Output: Meow!

In this example:

  • The Animal class defines an abstract method speak() using raise NotImplementedError, which forces subclasses to implement the method.
  • Both Dog and Cat override the speak() method, providing specific behavior.
  • The function make_animal_speak() demonstrates polymorphism by accepting any object that implements the speak() method, whether it's a Dog or a Cat.

6.7.4 Duck Typing in Python

Duck Typing is a concept in Python where the type or class of an object is determined by its behavior (i.e., the methods and attributes it supports), not by its explicit inheritance or class declaration. The term comes from the phrase:

"If it looks like a duck and quacks like a duck, it must be a duck."

In Duck Typing, if an object implements the required methods or attributes, it is treated as an instance of that class, regardless of its actual type.

Example of Duck Typing:

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

class Person:
    def quack(self):
        return "Person imitating a duck: Quack!"

# Polymorphic function using Duck Typing
def make_it_quack(duck_like_object):
    print(duck_like_object.quack())

# Both Duck and Person can "quack"
duck = Duck()
person = Person()

make_it_quack(duck)    # Output: Quack!
make_it_quack(person)  # Output: Person imitating a duck: Quack!

In this example:

  • Both Duck and Person implement the quack() method, even though they are not related by inheritance.
  • The make_it_quack() function accepts any object that implements a quack() method, demonstrating Duck Typing.
  • Duck Typing allows Python to focus on what an object can do (its behavior) rather than what it is (its class).

6.7.5 Polymorphism in Functions

Python functions can exhibit polymorphism by accepting different types of objects as parameters and calling methods that are implemented differently across the various classes.

Example:

class Rectangle:
    def area(self, length, width):
        return length * width

class Circle:
    def area(self, radius):
        return 3.14159 * radius ** 2

# Polymorphic function
def print_area(shape, *args):
    print(f"The area is: {shape.area(*args)}")

# Using polymorphism in a function
rect = Rectangle()
circle = Circle()

print_area(rect, 10, 5)   # Output: The area is: 50
print_area(circle, 7)     # Output: The area is: 153.93791

In this example:

  • Both Rectangle and Circle classes define an area() method, but they calculate the area in different ways based on their shapes.
  • The print_area() function exhibits polymorphism by calling the area() method on objects of different classes without needing to know the exact type of the object.

6.7.6 Polymorphism with Abstract Base Classes

Python provides Abstract Base Classes (ABC) in the abc module, which is a way to define a common interface for a group of related classes. Using ABCs ensures that subclasses implement certain methods, thus enforcing polymorphism more strictly.

Example:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

# Polymorphic behavior using abstract base classes
animals = [Dog(), Cat()]

for animal in animals:
    print(animal.speak())

In this example:

  • The Animal class is defined as an abstract base class using the ABC module.
  • The speak() method is marked as an abstract method with the @abstractmethod decorator, meaning any subclass of Animal must implement the speak() method.
  • This ensures that all subclasses (Dog, Cat) conform to the same interface, making polymorphism more structured.

6.7.7 Benefits of Polymorphism and Duck Typing

  • Flexibility: Polymorphism and Duck Typing allow you to write code that can work with objects of different types without knowing their specific class.
  • Code Reusability: You can write generic functions and methods that operate on different objects, leading to reusable code.
  • Extensibility: You can extend existing code by adding new classes that implement the same methods, without modifying the existing codebase.
  • Simplicity: Duck Typing in Python allows you to focus on an object’s behavior rather than its type, making your code cleaner and more Pythonic.

6.7.8 Summary

  • Polymorphism is the ability to use a common interface for objects of different types, allowing for flexible and reusable code.
  • Method overriding allows subclasses to provide specific implementations of methods defined in a parent class, enabling polymorphism through inheritance.
  • Duck Typing is a form of polymorphism where the type of an object is determined by its behavior rather than its explicit class. In Python, if an object implements the required methods, it can be used in place of any other object with the same methods.
  • Abstract Base Classes (ABC) can enforce polymorphism by requiring subclasses to implement certain methods, ensuring that all subclasses conform to the same interface.

Polymorphism and Duck Typing are powerful tools that allow you to write flexible, extensible, and maintainable code in Python. These concepts enable you to build systems where different objects can interact seamlessly, enhancing code reusability and simplicity.