Definition
A Protocol names the behavior an API needs. Classes can satisfy it without importing or inheriting the protocol, preserving ordinary duck typing while giving a checker a precise contract.
Describe the operation
from typing import Protocol
class SupportsClose(Protocol):
def close(self) -> None: ...
class Connection:
def __init__(self) -> None:
self.closed = False
def close(self) -> None:
self.closed = True
def finish(resource: SupportsClose) -> None:
resource.close()
connection = Connection()
finish(connection)
print("[check] compatible without protocol inheritance:", SupportsClose not in Connection.__mro__)
print("[state] connection closed:", connection.closed)
The checker sees Connection.close; runtime dispatch remains an ordinary method call.
Runtime checks are deliberately shallow
Decorating a protocol with @runtime_checkable enables isinstance, but the check looks for attribute presence rather than validating complete signatures or semantics. Use it only when that shallow runtime question is useful.
Common mistake
Avoid large protocols that mirror an entire concrete class. A protocol is most useful when it states the smallest capability needed at one boundary.
Primary sourceOfficial Python documentation ↗