python-protocols
Table of Contents
Python protocols
Python protocols (from typing.Protocol) define structural types—what methods and attributes an object must have, without requiring explicit inheritance. A class conforms to a protocol if it implements the right methods, whether or not it inherits from the protocol class. Protocols enable duck typing with static type checking.
Use protocols to define interfaces based on what objects can do, not what they inherit from.
Example
This example shows protocols enabling structural typing without inheritance.
# run: python3 protocols.py # description: structural typing with protocol from typing import Protocol, runtime_checkable # Define protocol (structural interface) @runtime_checkable class Drawable(Protocol): """Anything with a draw method.""" def draw(self) -> None: ... class Circle: def draw(self): print("Drawing circle") class Square: def draw(self): print("Drawing square") class Triangle: """Doesn't inherit from Drawable, but implements draw().""" def draw(self): print("Drawing triangle") # All conform to protocol, no inheritance needed def render(obj: Drawable): obj.draw() render(Circle()) render(Square()) render(Triangle()) # Runtime checking circle = Circle() print(f"Circle is Drawable: {isinstance(circle, Drawable)}") # Protocol with multiple methods @runtime_checkable class Serializable(Protocol): def to_dict(self) -> dict: ... def from_dict(self, data: dict) -> None: ... class User: def __init__(self, name): self.name = name def to_dict(self): return {"name": self.name} def from_dict(self, data): self.name = data["name"] def save(obj: Serializable): return obj.to_dict() user = User("Alice") print(f"Saved: {save(user)}") print(f"User is Serializable: {isinstance(user, Serializable)}") # Generic protocol from typing import TypeVar, Generic T = TypeVar('T') class Container(Protocol[T]): def add(self, item: T) -> None: ... def get(self) -> T | None: ... class Stack: def __init__(self): self.items = [] def add(self, item): self.items.append(item) def get(self): return self.items.pop() if self.items else None s: Container[int] = Stack() s.add(42) print(f"Stack value: {s.get()}")
Common patterns
Defining protocols:
class MyProtocol(Protocol):: define structural type- Methods with
...body (orpass): no implementation needed - Static type checkers infer protocol from method signatures
Runtime checkability:
@runtime_checkable: enableisinstance()checks- Without it, protocols only work with static type checkers
- Runtime checking is stricter; must match exactly
Methods in protocols:
- Just method signatures; no implementation (though can provide default)
- Methods are checked by presence and signature, not behavior
- If protocol requires
def foo(self, x: int) -> str:, any class with that method conforms
Generic protocols:
class Iterable(Protocol[T]):: protocol parameterized by type- Useful for containers, iterators, transformations
Combining protocols:
- Multiple
Protocolinheritance in class definition - Class must implement all methods from all protocols
Implicit vs explicit conformance:
- Implicit: class has right methods → conforms (duck typing + type checking)
- Explicit:
class Foo(ProtocolName):: but usually not needed - Point of protocols: conformance by structure, not inheritance
Static vs runtime checking:
- Static:
mypy,pyrightetc. check at development time - Runtime:
@runtime_checkable+isinstance()for checks at execution - Most code uses static checking; runtime is for introspection
Partial protocols:
- Can define
@runtime_checkableprotocol subset of larger interface - Class implements more than protocol requires; still conforms
When to use:
- Defining callback function signatures (functions called by framework)
- Plugin interfaces without inheritance overhead
- Type-safe duck typing with static type checking
- Generic data structures (Stack, Queue, Cache)
When ABCs are better:
- Need default implementations
- Want to prevent direct instantiation
- Need enforcement at runtime beyond type hints
- Class hierarchy makes logical sense
python-protocols.md · Last modified: by 127.0.0.1
