Protocols let functions accept any compatible object without forcing a shared base class.
Classic typing is nominal: a Dog is an Animal only if it subclasses Animal. Python code is usually duck-typed: it only cares that an object has the right methods. typing.Protocol brings duck typing to the type checker.
class SupportsClose(Protocol):
def close(self) -> None: ...Any class with a compatible close method matches SupportsClose, with no inheritance or registration. This is structural typing. The standard library uses it heavily: Iterable, Sized, Hashable and SupportsInt are protocols.
Tips:
- Keep protocols small, describing only what your function needs.
- Attributes can be protocol members too:
name: str. - Protocols can be generic:
class Reader[T](Protocol): def read(self) -> T: .... - A callback protocol with
__call__types callables that need keyword arguments or overloads, whichCallablecan't express.
By default protocols are only for the checker. Decorate one with @runtime_checkable to allow isinstance(obj, Proto), which only checks that the members exist, not their signatures, and can be slow.
Use a Protocol for what a function needs from its argument; use an ABC when you want to share implementation or force explicit opt-in.
Example
from typing import Protocol, runtime_checkable
@runtime_checkable
class Shape(Protocol):
name: str
def area(self) -> float: ...
class Square: # no inheritance from Shape
name = "square"
def __init__(self, side: float) -> None:
self.side = side
def area(self) -> float:
return self.side ** 2
class Label:
name = "label"
def report(shapes: list[Shape]) -> list[str]:
return [f"{s.name}: {s.area():.1f}" for s in shapes]
print(report([Square(2)])) # ['square: 4.0']
print(isinstance(Square(1), Shape), isinstance(Label(), Shape)) # True False
# report([Label()]) -> checker error: "Label" is missing "area"Remember
- A Protocol matches any class with compatible members, no inheritance needed
- Keep protocols small and focused on what the function uses
- @runtime_checkable enables isinstance, checking member presence only
Watch out for
- Relying on runtime_checkable isinstance to verify method signatures; it only checks names exist.