CodeNebula
Python🚀Advancedadvanced

Protocols (PEP 544) — Structural Subtyping

Python · Advanced · Run with python3

Explanation

`typing.Protocol` (Python 3.8+) defines structural interfaces — any class that implements the required methods satisfies the protocol, without explicit inheritance. This is static duck typing: `mypy` and `pyright` verify compliance at type-check time, not runtime. Use `@runtime_checkable` to enable `isinstance` checks. Protocols capture what code *does* rather than what it *inherits* — perfect for third-party classes you can't modify.

Real-World Scenario

A logging system accepts any object with a `write(message: str) -> None` method. `class Writable(Protocol): def write(self, msg: str) -> None: ...` lets `sys.stdout`, `io.StringIO`, a custom `SlackSink`, and a no-op `DevNull` all work with the same type-annotated code. ```python from typing import Protocol, runtime_checkable @runtime_checkable class Drawable(Protocol): def draw(self) -> str: ... def area(self) -> float: ... class Circle: def __init__(self, r: float): self.r = r def draw(self) -> str: return f"Circle(r={self.r})" def area(self) -> float: return 3.14159 * self.r ** 2 class Rectangle: def __init__(self, w: float, h: float): self.w, self.h = w, h def draw(self) -> str: return f"Rect({self.w}x{self.h})" def area(self) -> float: return self.w * self.h def render(shape: Drawable) -> None: print(f"{shape.draw()} — area: {shape.area():.2f}") shapes: list[Drawable] = [Circle(5), Rectangle(3, 4)] for s in shapes: render(s) print(isinstance(Circle(1), Drawable)) # True (runtime_checkable) ```

Quick Snippet

snippet.py
from typing import Protocol, runtime_checkable

@runtime_checkable
class Drawable(Protocol):
    def draw(self) -> str: ...
    def area(self) -> float: ...

class Circle:
    def __init__(self, r: float): self.r = r
    def draw(self) -> str:  return f"Circle(r={self.r})"
    def area(self) -> float: return 3.14159 * self.r ** 2

class Rectangle:
    def __init__(self, w: float, h: float): self.w, self.h = w, h
    def draw(self) -> str:  return f"Rect({self.w}x{self.h})"
    def area(self) -> float: return self.w * self.h

def render(shape: Drawable) -> None:
    print(f"{shape.draw()} — area: {shape.area():.2f}")

shapes: list[Drawable] = [Circle(5), Rectangle(3, 4)]
for s in shapes:
    render(s)

print(isinstance(Circle(1), Drawable))    # True (runtime_checkable)

Full Example

protocols.py
# Compile: python3 protocols.py
# Output:
# Circle(r=5.0) — area: 78.54
# Rect(3.0x4.0) — area: 12.00
# True

from typing import Protocol, runtime_checkable

@runtime_checkable
class Drawable(Protocol):
    def draw(self) -> str: ...
    def area(self) -> float: ...

class Circle:
    def __init__(self, r: float): self.r = r
    def draw(self) -> str:   return f"Circle(r={self.r})"
    def area(self) -> float: return 3.14159 * self.r ** 2

class Rectangle:
    def __init__(self, w: float, h: float): self.w, self.h = w, h
    def draw(self) -> str:   return f"Rect({self.w}x{self.h})"
    def area(self) -> float: return self.w * self.h

def render(shape: Drawable) -> None:
    print(f"{shape.draw()} — area: {shape.area():.2f}")

shapes: list[Drawable] = [Circle(5), Rectangle(3, 4)]
for s in shapes:
    render(s)

print(isinstance(Circle(1), Drawable))   # True
print(isinstance("not drawable", Drawable))  # False
Run command
python3 protocols.py