# 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. ```python # 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 (or `pass`): no implementation needed - Static type checkers infer protocol from method signatures **Runtime checkability**: - `@runtime_checkable`: enable `isinstance()` 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 `Protocol` inheritance 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`, `pyright` etc. 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_checkable` protocol 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