Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 11

Duck Typing and Polymorphism

Recommended resources

Hello! Welcome to your fifth lesson in our Object-Oriented Programming module.

In our last lesson, we focused on the internal design of our classes by applying encapsulation. We learned to use naming conventions like _protected and __private to hide data and provide controlled access through properties. This practice makes our classes more robust and maintainable.

Today, we shift our focus from the inside of a class to how different classes interact. We will explore polymorphism, a core OOP concept, and Python's unique approach to it known as duck typing. Your goal for this lesson is to be able to apply polymorphism using duck typing to write flexible and reusable code.

We will cover:

  • The core idea behind polymorphism and duck typing.
  • How to write functions that can operate on objects of different types.
  • The Pythonic principle of "Easier to Ask for Forgiveness than Permission" (EAFP) as a companion to duck typing.
  • How duck typing allows your custom objects to integrate seamlessly with Python's built-in functions.

This concept is fundamental to Python's design philosophy and is a key reason for the expressiveness of libraries you'll use in machine learning, like Pandas and Scikit-learn.

What is Polymorphism and Duck Typing?

Polymorphism comes from the Greek words for "many shapes." In programming, it means that a single interface (like a function or a method call) can work with objects of different types, and each object will respond in its own specific way.

Python implements polymorphism through a concept called duck typing. The name comes from the saying:

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

In Python terms, this means we don't care about an object's specific class or type. We only care about what it can do—that is, what methods and attributes it has. If a function needs an object that can .fly() and .swim(), it will work with any object that provides those methods, regardless of whether it's a Duck, a Swan, or a remote-controlled Drone.

Exploring Duck Typing in Python
This image illustrates the core idea of duck typing. The `Duck`, `Swan`, and `Albatross` classes are all different, but because they all have `swim()` and `fly()` methods, they can be used interchangeably by any code that expects those behaviors.

To see a practical demonstration, let's watch the first part of this excellent tutorial from Corey Schafer.

Python Tutorial: Duck Typing and Asking Forgiveness, Not Permission (EAFP)

This video clearly explains the concept of duck typing by contrasting a non-Pythonic approach (checking the object's type) with the Pythonic approach (just calling the methods).

Watch from the beginning until 04:05. Pay close attention to how the quack_and_fly function is simplified by removing the isinstance() check, allowing it to work with both Duck and Person objects.

As the video shows, the Pythonic way is not to ask "Is this object a Duck?" but rather to assume it can do what we need. This leads to more flexible and less restrictive code.

For someone with your Computer Science background, it's useful to contrast this with statically typed languages (like Java or C#). In those languages, type compatibility is checked at compile-time. Python, being a dynamically typed language, performs these checks at run-time, which is what makes duck typing possible. The emphasis is on the object's behavior, not its declared type.


Handling Potential Errors: EAFP

You might be thinking, "What happens if I pass an object that can't quack?" This is a valid concern. Just trying to call a method on an object that doesn't have it will raise an AttributeError.

The non-Pythonic way to handle this is called "Look Before You Leap" (LBYL). You would explicitly check if the object has the method before calling it:

# LBYL: Look Before You Leap (less Pythonic)
if hasattr(thing, 'quack'):
    thing.quack()

The preferred Pythonic style is "Easier to Ask for Forgiveness than Permission" (EAFP). This means you go ahead and try the operation, and if it fails, you catch the exception and handle it.

# EAFP: Easier to Ask Forgiveness than Permission (more Pythonic)
try:
    thing.quack()
except AttributeError:
    print("This object can't quack.")

Let's continue with Corey Schafer's video, where he explains this principle perfectly.

Python Tutorial: Duck Typing and Asking Forgiveness, Not Permission (EAFP)

This part of the video introduces the EAFP principle, showing how to use a try...except block to gracefully handle cases where an object doesn't have the expected method.

Watch from 04:05 to 07:17. Notice how much cleaner and more readable the try...except block is compared to the series of hasattr checks.

EAFP is generally favored in Python because it's more optimistic and often more readable, especially when you need to perform several operations. It avoids cluttering your code with checks and focuses on the "happy path" first.

Test your understanding!

Imagine you have a function process_data(items) that iterates through a list of objects. For each object, it should call a .to_json() method if it exists. If it doesn't, it should fall back to using Python's built-in str() function to get a string representation.

How would you write this function using the EAFP principle?

Show answer
def process_data(items):
    results = []
    for item in items:
        try:
            # First, try to call the .to_json() method
            results.append(item.to_json())
        except AttributeError:
            # If it fails, ask for forgiveness and use str()
            results.append(str(item))
    return results

# Example Usage:
class User:
    def __init__(self, name):
        self.name = name
    def to_json(self):
        return f'{{"name": "{self.name}"}}'

class Product:
    def __init__(self, product_id):
        self.product_id = product_id
    def __str__(self):
        return f"Product ID: {self.product_id}"

data_list = [User("Alice"), Product(123), User("Bob")]
processed = process_data(data_list)
print(processed)
# Expected Output: ['{"name": "Alice"}', 'Product ID: 123', '{"name": "Bob"}']

Duck Typing and Python's "Protocols"

One of the most powerful aspects of duck typing is how it integrates custom classes with Python's built-in functions. How does Python's len() function know the length of a string, a list, and a dictionary? It uses duck typing!

When you call len(my_object), Python looks for a special method on that object called __len__. If it finds it, it calls it. This informal requirement—having a __len__ method to work with len()—is called a protocol.

By implementing these special "dunder" (double-underscore) methods, you can make your own objects compatible with Python's syntax and functions, making them feel like native types.

Why Everyone is always Talking About Duck Typing in Python.

This video provides a fantastic demonstration of making a custom class 'Pythonic' by implementing protocols. It shows how adding methods like __len__ and __iter__ allows a custom object to work with len() and for loops.

Watch from 05:15 to 08:59. See how the ToDoList class evolves from using clunky getter methods (get_length(), get_todos()) to implementing __len__ and __iter__, resulting in much more intuitive and readable code.


A Practical Application: From Math to Machine Learning

Duck typing is especially powerful in numerical and scientific computing. You can write an algorithm for one type of number (e.g., a standard float), and thanks to duck typing, it can often work on entirely different data structures, like NumPy arrays, without any modification.

The following resource illustrates this with the Newton-Raphson method for finding roots of an equation. The code is written to work with simple numbers but is then applied to NumPy arrays of complex numbers to generate a fractal, and it works perfectly. This is a direct consequence of duck typing and is highly relevant to your goal of working with ML, where NumPy is a cornerstone.

Duck typing and interfaces

This article, 'Duck typing and interfaces', demonstrates a powerful, real-world application of duck typing in a mathematical context that is highly relevant to machine learning.

Read the first part of the article, starting from the introduction down to the end of the code that generates the Newton fractal. Pay attention to how the newton function, originally designed for real numbers, is seamlessly used with numpy arrays of complex numbers.


Hands-on Practice: The Media Player

Now it's time to apply these concepts yourself. This exercise will challenge you to build a small system that uses polymorphism both through traditional inheritance and through pure duck typing.

Python Polymorphism - Duck Typing, Method Overriding, Protocols

This article, 'Python Polymorphism', provides a great summary and a practical exercise. You will build a polymorphic media player.

First, read the sections 'Duck Typing', 'EAFP: Easier to Ask Forgiveness than Permission', and 'Common Mistakes' to solidify your understanding. Then, tackle the 'Exercise: Media Player' at the end. Try to solve it on your own before looking at the provided solution.


Conclusion

In this lesson, you've explored polymorphism, one of the pillars of OOP, and learned about Python's powerful and flexible implementation of it: duck typing.

Key Takeaways:

  • Polymorphism allows different objects to respond to the same method call in their own unique way.
  • Duck Typing is Python's approach: it focuses on an object's behavior (what methods it has) rather than its explicit type.
  • The EAFP principle ("Easier to Ask for Forgiveness than Permission") is the standard way to handle potential errors when using duck typing, favoring try...except blocks over explicit checks.
  • By implementing special protocols (like __len__ and __iter__), you can make your custom objects integrate seamlessly with Python's built-in functions.

Preview of the Next Lesson:
Duck typing is incredibly flexible, but sometimes you need to be more explicit about what methods a class is expected to implement. In our next lesson, we will explore Abstract Base Classes (ABCs). ABCs act as a formal blueprint or contract, allowing you to define an interface that other classes must adhere to, combining some of the formality of static typing with the flexibility of Python.