Introduction
When developing Python applications, we often want our functions to work with objects that share certain behavior rather than belonging to a specific class hierarchy. For example, you might need a function that can accept any object as long as it has a write method—whether it’s a file object, a buffer, or a custom class. The challenge is how to enforce this requirement using type hints in a way that is both flexible and Pythonic.

Traditionally, developers might rely on inheritance from an abstract base class (ABC) to guarantee such behavior. However, Python offers a more dynamic and practical approach: Protocols from the typing module. A Protocol allows you to define the expected methods or attributes an object should implement, without enforcing inheritance.
Here’s an example:
from typing import Protocol
class Writable(Protocol):
def write(self, s: str) -> None: ...
def save_message(obj: Writable, message: str) -> None:
obj.write(message)

In this example, any object passed to save_message must have a write method that accepts a string. The function does not care whether the object is a file, a socket, or a custom class—only that it follows the Writable protocol. This aligns with Python’s duck typing philosophy: “If it looks like a duck and quacks like a duck, it’s a duck.” Type checkers like mypy will enforce this contract during static analysis, making your code more reliable.

Conclusion
To summarize, when you want a function to work with any object that has a write method—regardless of class inheritance—the best approach is to use a Protocol from the typing module. This method provides both flexibility and safety, allowing you to embrace Python’s dynamic nature while still enjoying the benefits of static type checking.