Protocols and Duck Typing in Python
Description:
In Python, a protocol is an informal interface definition that enables polymorphism through agreed-upon method signatures. Protocols do not rely on inheritance; instead, they are based on the concept of "Duck Typing": as long as an object implements the methods required by the protocol, it can be used as a type of that protocol. For example, the iterator protocol requires the implementation of __iter__ and __next__ methods. Understanding protocols helps in writing flexible and extensible code.
Problem-solving Process:
-
Basic Concept of Protocols
- A protocol is a set of methods without the need for explicit declaration (like Java's
interface). - For example, the "callable protocol" requires implementing the
__call__method, and the "context manager protocol" requires implementing__enter__and__exit__methods. - Code example:
class Adder: def __call__(self, x, y): return x + y add = Adder() print(add(3, 5)) # The object `add` conforms to the callable protocol
- A protocol is a set of methods without the need for explicit declaration (like Java's
-
Practical Application of Duck Typing
- Functions do not check the type of arguments but instead check if they support the required operations.
- Example: A function requires parameters to support
len()and indexing operations (i.e., the sequence protocol):def get_first_item(container): if len(container) > 0: return container[0] return None # Both lists and strings support the sequence protocol, so they can be passed in print(get_first_item([1, 2, 3])) # Output: 1 print(get_first_item("Hello")) # Output: 'H'
-
Protocols in Static Type Checking
- Using the
typing.Protocolclass (Python 3.8+), protocols can be explicitly defined for type hints. - Example: Defining a "closable" protocol:
from typing import Protocol class Closable(Protocol): def close(self) -> None: ... def safe_close(resource: Closable) -> None: resource.close() class File: def close(self) -> None: print("File closed") safe_close(File()) # The File class implicitly conforms to the Closable protocol
- Using the
-
Examples of Common Built-in Protocols
- Iterator Protocol:
__iter__(returns an iterator) and__next__(returns the next item). - Context Manager Protocol:
__enter__(called upon entry) and__exit__(called upon exit). - Comparison Protocol:
__eq__,__lt__, etc., used for operator overloading.
- Iterator Protocol:
-
Differences Between Protocols and Abstract Base Classes (ABC)
- Abstract base classes (e.g.,
collections.abc.Iterable) explicitly declare interfaces through inheritance, whereas protocols rely solely on method implementation. - Protocols are more flexible, allowing unrelated classes to implicitly conform to an interface, while ABCs require registration or inheritance.
- Abstract base classes (e.g.,
-
Practice: Custom Protocols
- Suppose a "serializable" protocol is needed, requiring the implementation of a
to_jsonmethod:from typing import Protocol class JSONSerializable(Protocol): def to_json(self) -> str: ... def save_json(obj: JSONSerializable) -> None: print(obj.to_json()) class Person: def to_json(self) -> str: return '{"name": "Alice"}' save_json(Person()) # The Person class implicitly conforms to the protocol
- Suppose a "serializable" protocol is needed, requiring the implementation of a
Summary:
Protocols are one of the core mechanisms of Python's dynamic typing, enabling polymorphism through method signature conventions. Combining typing.Protocol allows for explicit constraints in static type checking while maintaining code flexibility.