What is duck typing in Python

Duck typing is when the type or class of an object is deduced from its behavior, including its methods and properties, rather than its explicit type or class. It’s said to be so called because:

“If it walks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.”

In Python, that’s saying:

  • You don’t have to check whether an object belongs to a given type.
  • It does not matter what the actual type of the object is, as long as it has the necessary methods or properties.

Key Principles of Duck Typing

1. Focus on Behavior:

  • Objects are defined by what they can do, not by their specific class or inheritance.

2. No Explicit Type Checking:

  • Instead of checking the type of an object with isinstance or type, you directly use the methods or attributes you need. If they exist, the code works; otherwise, it raises an error.

3. Polymorphism Without Inheritance:

  • Different objects can be used interchangeably as long as they support the same operations.

Examples of Duck Typing in Python

Without Duck Typing (Explicit Type Checking)

def process(obj):
    if isinstance(obj, list):
        return [x * 2 for x in obj]
    elif isinstance(obj, tuple):
        return tuple(x * 2 for x in obj)
    else:
        raise TypeError("Unsupported type")

# Examples
print(process([1, 2, 3]))  # Output: [2, 4, 6]
print(process((1, 2, 3)))  # Output: (2, 4, 6)
print(process("123"))      # Raises TypeError

Output:

[2, 4, 6]
(2, 4, 6)
TypeError: Unsupported type

With Duck Typing

def process(obj):
    return [x * 2 for x in obj]  # Assumes obj is iterable

# Examples
print(process([1, 2, 3]))  # Output: [2, 4, 6]
print(process((1, 2, 3)))  # Output: [2, 4, 6]
print(process(range(3)))   # Output: [0, 2, 4]
print(process("123"))      # Output: ['11', '22', '33']

Output:

[2, 4, 6]
[2, 4, 6]
[0, 2, 4]
['11', '22', '33']

In the second version, process works with any iterable object (like lists, tuples, strings, or ranges), without needing explicit type checks.

Advantage of Duck Typing

1. Flexibility:

  • Functions take any object providing the required methods, so the functions are more flexible.

2. Easy:

  • Reduces boilerplate codes as it has no need of explicit type check.

3. Supporting Polymorphism:

  • It ensures that the use of objects in a similar kind is possible, without being related with inheritance.

4. Reuse:

  • Generic function can work for different types of objects.

Disadvantages of Duck Typing

1. Runtime Errors:

  • If an object does not have the required method or attribute, a TypeError or AttributeError is raised at runtime.

2. Less Explicit Contracts:

  • It may be unclear what behaviors a function expects, making the code harder to read and maintain.

3. Testing Overhead:

  • You must write tests to ensure objects passed to functions behave correctly.

Practical Example: File-Like Objects

Duck typing is commonly used for “file-like objects” in Python. These objects do not need to be real files, they just need to implement read or write methods.

Example:

def read_file(file_obj):
    content = file_obj.read()
    print(content)

# Using a real file
with open("example.txt", "w") as f:
    f.write("This is a real file.")

with open("example.txt", "r") as f:
    read_file(f)  # Output: This is a real file.

# Using a custom file-like object
class StringFile:
    def __init__(self, string):
        self.string = string

    def read(self):
        return self.string

fake_file = StringFile("This is a fake file.")
read_file(fake_file)  # Output: This is a fake file.

Output (assuming example.txt exists):

This is a real file.
This is a fake file.

Here, the read_file function works with both a real file and a custom object because both implement the read method.

Polymorphism in Duck Typing

Duck typing enables polymorphism naturally. Let’s demonstrate with another example:

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

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

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

def make_animal_speak(animal):
    print(animal.speak())

# Examples
dog = Dog()
cat = Cat()
duck = Duck()

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

Output:

Woof!
Meow!
Quack!

Here, make_animal_speak works with any object that has a speak method, regardless of its class.

Summary

Duck typing is one of Python’s key strengths. It encourages flexibility and simplicity but requires careful coding to handle potential runtime errors. By focusing on an object’s behavior rather than its type, you can write highly reusable and polymorphic code.