Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A Protocol in Python is a base class provided by the typing module that enables structural subtyping (often referred to as static duck typing). Introduced in PEP 544, it allows developers to define an interface based on the presence of specific methods and attributes, rather than requiring explicit inheritance (nominal subtyping). If a class implements the members defined in a Protocol with matching type signatures, static type checkers recognize it as a subtype of that Protocol.

Syntax and Definition

A Protocol is defined by inheriting from typing.Protocol. The interface shape is established using type hints and the ... (Ellipsis) literal for method bodies, indicating that the implementation is deferred to the structural subtype.
from typing import Protocol

class DataHandler(Protocol):
    # Defines a required attribute
    buffer_size: int 

    # Defines a required method signature
    def process(self, payload: bytes) -> bool:
        ...

Structural vs. Nominal Subtyping

Python traditionally relies on nominal subtyping via Abstract Base Classes (abc.ABC). In nominal subtyping, a class must explicitly declare its relationship to the interface. Protocol bypasses this requirement.

# Implicitly satisfies DataHandler without inheriting from it
class StreamProcessor:
    def __init__(self) -> None:
        self.buffer_size = 1024

    def process(self, payload: bytes) -> bool:
        return len(payload) < self.buffer_size


# Static type checkers evaluate this as valid:
def execute_handler(handler: DataHandler) -> None:
    handler.process(b"data")

execute_handler(StreamProcessor()) # Valid structural subtype

Runtime Behavior and Introspection

By default, Protocols exist purely for static type analysis (e.g., via mypy or pyright). They are erased at runtime. Attempting to use isinstance() or issubclass() against a standard Protocol will raise a TypeError. To enable runtime introspection, the Protocol must be decorated with typing.runtime_checkable.
from typing import Protocol, runtime_checkable

@runtime_checkable
class Serializable(Protocol):
    def serialize(self) -> str:
        ...

class User:
    def serialize(self) -> str:
        return "UserObject"


# Valid at runtime due to @runtime_checkable
is_serializable = isinstance(User(), Serializable)  # Evaluates to True
Technical Caveat: @runtime_checkable only verifies the presence of the required attributes and methods. Because Python does not enforce type hints at runtime, it cannot verify that the method signatures or attribute types of the instance actually match the Protocol’s definition.

Generic Protocols

Protocols can be combined with TypeVar to create generic structural interfaces. The Protocol class inherently supports the mechanics of typing.Generic.
from typing import Protocol, TypeVar

T = TypeVar('T')

class Container(Protocol[T]):
    def add(self, item: T) -> None:
        ...
    
    def get(self) -> T:
        ...

Callable Protocols

While typing.Callable is sufficient for simple function signatures, Protocols can define complex callable signatures by implementing the __call__ dunder method. This is necessary when a function type requires overloaded signatures, keyword-only arguments, or generic type variables that depend on each other.
from typing import Protocol

class ComplexCallback(Protocol):
    def __call__(self, data: str, *, strict: bool = False) -> int:
        ...
Master Python with Deep Grasping Methodology!Learn More