Duck typing
Overview
Duck typing is a programming concept where the type or the class of an object is less important than the methods it defines. When you use duck typing, you do not check types at all. Instead, you check for the presence of a given method or attribute. If it walks like a duck and quacks like a duck, it must be a duck. For example, if a function expects a file-like object, it doesn't need an actual `file` object; it just needs any object that has a `.read()` method. The mental model is: Python doesn't care *what* an object is, only *what it can do*.
It allows for highly flexible and reusable code. You can pass completely different objects into the same function, as long as they implement the expected interface.
Where used: FastAPI, Pydantic, Django, Pytest
Why learn this
- Write functions that accept a wider variety of inputs
- Easily mock objects in tests without complicated inheritance
- Understand why Python doesn't enforce strict types at runtime
Code walkthrough
class Duck:
def quack(self):
print('Quack')
class Person:
def quack(self):
print('I am quacking like a duck!')
def make_it_quack(thing):
thing.quack()
duck = Duck()
person = Person()
make_it_quack(duck)
make_it_quack(person)
Focus: thing.quack()
Aha moment
def get_length(item):
return len(item)
print(get_length('hello'))
print(get_length([1, 2, 3]))
print(get_length({'a': 1, 'b': 2}))
Prediction: What happens when we pass different types (string, list, dict) to `get_length`?
Common guess: It will throw a TypeError because `item` doesn't have a specific type.
Python uses duck typing for the `len()` function. As long as the object implements the `__len__` method, it works perfectly, regardless of the object's actual type.
Common mistakes
- Unintended type errors: Assuming an object has a method just because it's named similarly, causing an `AttributeError` at runtime.
- Over-checking types: Using `isinstance()` everywhere defeats the purpose of Python's dynamic nature. The fix is to use `hasattr()` or `try/except` (EAFP).
Glossary
- interface
- The specific methods and properties that an object is expected to have so it can be used in a certain way. Example: `hasattr(obj, 'read')`
- rigid
- Code that is inflexible and hard to change or adapt because it strictly enforces rules, like exact data types. Example: `if type(obj) == int:`
- EAFP
- Easier to Ask Forgiveness than Permission — try the operation and catch the exception, instead of checking first. Example: `try: val = d['key']`
Recall questions
- What is the core idea of duck typing in Python?
Understanding checks
If you use duck typing, how do you handle cases where an object passed to a function does not have the required method?
You rely on standard Python exceptions (like `AttributeError`) or handle them using a `try/except` block (EAFP paradigm).
With duck typing, Python doesn't check the type upfront, so if a method is missing, the code fails dynamically at runtime.
What is wrong with the following attempt at using duck typing?
It explicitly checks the type of the `parser` argument instead of just invoking `.parse()`, which defeats the purpose of duck typing.
Duck typing means relying on the presence of a method or attribute, rather than checking the class type. The code should simply be `return parser.parse(text)`.
Practice tasks
Implement a flexible logger
Modify the `log_data` function to remove the type checking logic. It should rely on duck typing instead, simply calling the `log()` method on the provided object. Then modify `FileLogger` to have the correct method name so it works.
Challenge
Flexible Data Exporter
Create an `export_data(exporter, data)` function. The `exporter` must have an `export(data)` method. Create a `CSVExporter` that prints the data as comma-separated values, and a `JSONExporter` that prints it as a JSON string. Prove they both work when passed to `export_data`.