Concept lesson

Python Protocols vs ABCs

Static structural subtyping with typing.Protocol vs nominal Abstract Base Classes.

lesson
Freshness: current15 min read
Mastery
not started · 0%

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).

Protocol Interface Definition
Unrelated Class implementing methods
Static Type Checker (mypy / pyright)
Structural Compatibility Verified
Conceptual teaching model synthesized from:Python 3.12 AsyncIO Event Loop & Asynchronous I/O Specification

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 (TypeError if missing).
  • typing.Protocol (Structural): Checked statically at compile time without runtime inheritance overhead.

Failure modes and misconceptions

  1. Protocol runtime checks: By default, isinstance(obj, Protocol) raises a TypeError unless annotated with @runtime_checkable.
  2. Metaclass over-engineering: Using custom metaclasses for simple validation adds unnecessary magic; use Pydantic models or Protocols instead.
Reflect before revealing the guide

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

not-started · 0%
theory0%
decision0%
activityNot mapped
projectNot mapped
1. How does static structural subtyping with typing.Protocol differ from abc.ABC?
2. What decorator is required to allow isinstance(obj, MyProtocol) runtime checks?
3. What is a drawback of using complex custom metaclasses (type subclasses) for data validation?

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