Learning outcomes
- Implement static duck typing with Protocols
- Compare structural and nominal interfaces
Mental model
Python supports both nominal subtyping (Abstract Base Classes requiring explicit inheritance class Custom(ABC)) and structural subtyping (typing.Protocol requiring only matching interface shapes without inheritance).
Theory
A Protocol (PEP 544) defines a contract by interface shape rather than explicit class hierarchy. Any class that implements the methods and attributes defined in a Protocol is considered a valid subtype by static type checkers (mypy, Pyright), embodying static "duck typing" without tight coupling.
from typing import Protocol, runtime_checkable
@runtime_checkable
class Renderable(Protocol):
def render(self) -> str:
...
class HTMLCard:
def render(self) -> str:
return "<div class='card'>HTML Payload</div>"
class MarkdownCard:
def render(self) -> str:
return "## Markdown Payload"
def display_component(item: Renderable) -> str:
return item.render()
Alternatives and trade-offs
abc.ABC(Nominal): Enforces method implementation at runtime instantiation (TypeErrorif missing).typing.Protocol(Structural): Checked statically at compile time without runtime inheritance overhead.
Failure modes and misconceptions
Protocolruntime checks: By default,isinstance(obj, Protocol)raises aTypeErrorunless annotated with@runtime_checkable.- Metaclass over-engineering: Using custom metaclasses for simple validation adds unnecessary magic; use Pydantic models or Protocols instead.
Decision scenario
Use typing.Protocol to define contracts for third-party pluggable adapters (database clients, notification channels) so third-party teams do not need to inherit from your internal base classes.
Learning outcomes
- Contrast structural subtyping (
typing.Protocol) with nominal subtyping (abc.ABC). - Enable runtime
isinstance()checks using@runtime_checkable. - Design decoupled, testable Python systems with static type guarantees.
Trade-offs
Protocols enable static duck typing without explicit inheritance coupling, but require static type checkers (mypy/pyright) in CI pipelines to enforce contracts.
Evidence assessment
Theory and decision mastery
Decision scenario
An enterprise Python codebase has multiple database adapters (PostgreSQL, MongoDB, DynamoDB) developed by different third-party teams.
Which interface design allows writing decoupled business logic that accepts any valid database adapter?
Primary sources
- Python 3.12 AsyncIO Event Loop & Asynchronous I/O Specification — Python Software Foundation, verified 2026-07-22