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.
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()}")
Defining protocols:
class MyProtocol(Protocol):: define structural type... body (or pass): no implementation neededRuntime checkability:
@runtime_checkable: enable isinstance() checksMethods in protocols:
def foo(self, x: int) -> str:, any class with that method conformsGeneric protocols:
class Iterable(Protocol[T]):: protocol parameterized by typeCombining protocols:
Protocol inheritance in class definitionImplicit vs explicit conformance:
class Foo(ProtocolName):: but usually not neededStatic vs runtime checking:
mypy, pyright etc. check at development time@runtime_checkable + isinstance() for checks at executionPartial protocols:
@runtime_checkable protocol subset of larger interfaceWhen to use:
When ABCs are better: