Polymorphism and Duck Typing in Python


Table of Contents

  • Introduction to Polymorphism
  • Types of Polymorphism
  • Polymorphism in Python with Examples
  • What is Duck Typing?
  • Duck Typing vs Traditional Typing
  • Practical Examples of Duck Typing
  • Best Practices for Polymorphism and Duck Typing
  • Conclusion

Introduction to Polymorphism

Polymorphism is a core concept in Object-Oriented Programming (OOP) that allows objects of different classes to be treated as objects of a common superclass. The word “polymorphism” means “many forms,” and it refers to the ability of different classes to be used interchangeably if they share a common interface or behavior.

In simple terms, polymorphism allows different classes to define methods that are called through the same name but behave differently depending on the object’s class. This flexibility leads to more maintainable, scalable, and reusable code.

Python, being a dynamically typed language, supports polymorphism naturally and elegantly.


Types of Polymorphism

Polymorphism can be broadly categorized into two types:

  1. Compile-Time Polymorphism (Static Polymorphism)
    This type of polymorphism is resolved during compile time. Common examples include method overloading and operator overloading. Python, however, does not support traditional method overloading found in languages like Java or C++. Instead, it handles overloading through default arguments and variable-length arguments.
  2. Run-Time Polymorphism (Dynamic Polymorphism)
    This type of polymorphism is resolved at runtime. In Python, method overriding is a common form of run-time polymorphism where a child class provides a specific implementation of a method that is already defined in its parent class.

Polymorphism in Python with Examples

Let us understand polymorphism through examples:

Example: Polymorphism with Functions and Objects

class Dog:
def sound(self):
print("Barks")

class Cat:
def sound(self):
print("Meows")

def make_sound(animal):
animal.sound()

# Creating instances
dog = Dog()
cat = Cat()

# Function calls
make_sound(dog) # Output: Barks
make_sound(cat) # Output: Meows

In this example, the make_sound function takes any object that has a sound() method, regardless of its class type. Both Dog and Cat classes implement the sound() method differently. This is a perfect demonstration of polymorphism, where the method to be executed is determined at runtime based on the object passed.


What is Duck Typing?

Duck Typing is a concept closely related to polymorphism in dynamically typed languages like Python. The name comes from the saying:

"If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck."

In programming terms, if an object behaves like a duck (i.e., provides the necessary methods and properties expected), it is treated like a duck, regardless of its actual type.

Python programmers do not focus on the type of an object but rather on whether the object supports certain behavior (methods or properties). If it does, it is accepted. This flexibility makes Python highly expressive and reduces the need for heavy type hierarchies.


Duck Typing vs Traditional Typing

FeatureTraditional TypingDuck Typing
FocusType of the objectBehavior of the object
Example LanguagesJava, C++Python, Ruby
FlexibilityLess flexible, strict type checkingHighly flexible, behavior-based
Compilation/RuntimeType checked at compile-timeBehavior checked at runtime
UsageInterfaces and abstract classes are commonProtocols or expected behavior is sufficient

In traditional statically typed languages, the type of an object must be explicitly declared and matched. In Python’s duck typing system, what matters is whether an object has the required methods and properties, not its type.


Practical Examples of Duck Typing

Example 1: Using Duck Typing in Functions

class Duck:
def quack(self):
print("Quack, quack!")

class Person:
def quack(self):
print("I can imitate a duck!")

def perform_quack(entity):
entity.quack()

duck = Duck()
person = Person()

perform_quack(duck) # Output: Quack, quack!
perform_quack(person) # Output: I can imitate a duck!

In this example, the perform_quack function calls the quack() method without caring whether the object is an instance of Duck or Person. As long as the object has a quack method, it works.

Example 2: More Real-World Use Case

Consider a function that reads content from different sources:

class FileReader:
def read(self):
print("Reading from a file...")

class WebReader:
def read(self):
print("Reading from a website...")

def read_content(reader):
reader.read()

file_reader = FileReader()
web_reader = WebReader()

read_content(file_reader) # Output: Reading from a file...
read_content(web_reader) # Output: Reading from a website...

Here, both FileReader and WebReader classes implement a read method. The read_content function doesn’t care about the type of reader as long as it provides a read() method.


Best Practices for Polymorphism and Duck Typing

While duck typing offers flexibility, it comes with potential risks if not used carefully. Follow these best practices:

  • EAFP Principle: Embrace the “Easier to Ask for Forgiveness than Permission” coding style. Try the operation directly and handle exceptions if it fails, rather than checking types explicitly. try: entity.quack() except AttributeError: print("The object cannot quack.")
  • Avoid Unclear Errors: If an object does not have the expected method, it may raise an AttributeError. Make sure to document clearly what behavior your function expects.
  • Use Abstract Base Classes or Protocols: From Python 3.8+, you can use the typing.Protocol module to define informal interfaces. It allows you to declare the methods you expect without forcing strict inheritance. from typing import Protocol class Reader(Protocol): def read(self) -> None: ... def fetch_content(reader: Reader): reader.read()
  • Document Assumptions: Always document what behavior (methods/properties) an object should support for your function to work correctly.

Conclusion

Polymorphism and duck typing are fundamental to writing clean, flexible, and maintainable Python code. By leveraging polymorphism, you can design systems that are easy to extend and adapt. Duck typing, when used wisely, removes unnecessary type restrictions and focuses on the actual behavior of objects, resulting in more natural and expressive code.

Understanding how to balance the power of polymorphism and duck typing with appropriate precautions will make you a much more effective Python programmer, capable of writing code that is both powerful and readable across large-scale applications.

In the world of Python development, if it behaves like a duck, you can — and should — treat it like a duck.

Syskoolhttps://syskool.com/
Articles are written and edited by the Syskool Staffs.