A Protocol (introduced in PEP 544 and available via typing.Protocol) defines a structural interface — a set of methods and attributes that a class must have to be considered compatible. Unlike Abstract Base Classes, a Protocol does not require inheritance: any class that happens to have the right methods satisfies the Protocol automatically. This is called structural subtyping and aligns with Python's duck typing philosophy.
For example, you might define class Drawable(Protocol): def draw(self) -> None: .... Any class with a draw() method is considered a Drawable by static type checkers like mypy, without needing to inherit from Drawable or register with it. This makes Protocols ideal for defining interfaces across library boundaries where you cannot control the class hierarchy.
Protocols can be made runtime-checkable with the @runtime_checkable decorator, which enables isinstance() checks (though only for method signatures, not implementations). Protocols can also define attributes and class variables. They represent the modern Pythonic approach to interface definition: explicit enough for static analysis, flexible enough for duck typing, and completely non-invasive — the implementing class does not even need to know the Protocol exists.
Related terms: Duck Typing, Abstract Base Class, Type Hint
Discussed in:
- Chapter 10: Inheritance and Composition — Duck Typing and Protocols