Protocol and structural typing
Protocol (PEP 544) gives Python static duck typing — types that match by shape (methods/attributes) rather than by inheritance.
The problem
Without protocols, expressing “anything with a read() method” requires either an ABC (which classes must explicitly inherit from) or Any:
from typing import IO # too restrictive — what if it's a custom class?
def consume(f: IO) -> str: ...
With a Protocol, any class that happens to have the right methods qualifies:
from typing import Protocol
class Readable(Protocol):
def read(self, n: int = -1) -> str: ...
def consume(f: Readable) -> str:
return f.read()
class MyReader:
def read(self, n: int = -1) -> str:
return "data"
consume(MyReader()) # MyReader matches Readable structurally
consume(open("x.txt")) # file objects also match
MyReader doesn’t inherit from Readable. It just has the right method shape.
runtime_checkable
By default, isinstance(x, MyProtocol) raises. Add the decorator if you need it:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Closeable(Protocol):
def close(self) -> None: ...
if isinstance(obj, Closeable):
obj.close()
Caveats: runtime check only verifies method names exist, not signatures or types. It’s slow (uses hasattr).
Common standard protocols
from typing import Iterable, Iterator, Sized, Container, Hashable
# These are already Protocols (or ABCs that act like ones)
class Sized(Protocol):
def __len__(self) -> int: ...
class Iterable(Protocol):
def __iter__(self) -> Iterator: ...
So def f(x: Sized) -> int: return len(x) accepts anything with __len__.
Protocols vs ABCs
| Aspect | ABC | Protocol |
|---|---|---|
| Subclass declaration | required (class Foo(ABC):) |
none — structural |
| Compile-time only? | No, ABCs check at instantiation | Yes (unless runtime_checkable) |
| Method enforcement | enforced when subclass is instantiated | only checked statically |
| Mixing with third-party types | requires registering | works automatically |
Use Protocols when you want to type-check interfaces without forcing inheritance. Use ABCs when you want runtime contract enforcement and shared default methods.
Generic protocols
Protocols can be generic:
from typing import Protocol, TypeVar
T = TypeVar("T")
class Container(Protocol[T]):
def __contains__(self, item: T) -> bool: ...
def has_zero(c: Container[int]) -> bool:
return 0 in c
Interview angle
“What’s the difference between an ABC and a Protocol?” (ABC = nominal subtyping, Protocol = structural / duck typing.) “When would you use each?” (Protocol for typing existing third-party objects without modifying them; ABC when you want shared implementation + runtime enforcement.)