Structural Subtyping with Protocol
If you have used ABCs to define interfaces, Protocol offers a different approach. Rather than requiring explicit inheritance, Protocol lets you define interfaces that types can satisfy just by having the right methods. This is structural subtyping — Python checks the structure, not the inheritance hierarchy.
The Problem with Duck Typing
Python has always embraced duck typing: if an object has the right methods, you can use it. The problem is that static type checkers like mypy cannot verify what methods an argument needs:
def draw_shape(shape):
shape.draw()
class Circle:
def draw(self):
print("Drawing circle")
class Square:
def draw(self):
print("Drawing square")
draw_shape(Circle())
draw_shape(Square())
You cannot tell from the function signature what methods the argument needs. This is fine at runtime but frustrating for static analysis.
Enter Protocol
typing.Protocol lets you define interfaces explicitly without requiring inheritance:
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ...
Now you can annotate your function:
def render(drawable: Drawable) -> None:
drawable.draw()
Any object with a draw() method that returns None satisfies this Protocol — no inheritance required:
class Circle:
def draw(self) -> None:
print("Drawing circle")
class Square:
def draw(self) -> None:
print("Drawing square")
render(Circle())
render(Square())
This is structural subtyping: the type checker verifies the object has the right structure, not whether it inherits from a particular class.
When to Use Protocol
Protocol shines in these scenarios:
1. You cannot modify the class you are adapting A third-party library class already has the methods you need, but does not inherit from your interface:
from typing import Protocol
class Serializable(Protocol):
def to_json(self) -> str: ...
class Response:
def __init__(self, text):
self.text = text
def to_json(self) -> str:
return self.text
def serialize(data: Serializable) -> str:
return data.to_json()
2. Multiple unrelated classes share behavior Several classes implement the same methods but share no common ancestor:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def distance_from_origin(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
class Vector:
def __init__(self, components):
self.components = components
def distance_from_origin(self) -> float:
return sum(x**2 for x in self.components) ** 0.5
Both can satisfy a HasMagnitude Protocol without sharing inheritance.
Protocol vs ABC
| Aspect | ABC | Protocol |
|---|---|---|
| Inheritance | Required | Not required |
| Runtime checks | isinstance() works | No runtime effect |
| Implementation | Override abstract methods | Just implement the methods |
| Use case | Enforce contract strictly | Adapting existing types |
ABC forces subclasses to inherit and implement methods. Protocol merely describes what methods an object should have — the type checker verifies it, not the class itself.
from abc import ABC, abstractmethod
from typing import Protocol
class Animal(ABC):
@abstractmethod
def speak(self) -> str: ...
class Dog(Animal):
def speak(self) -> str:
return "Woof"
class Cat:
def speak(self) -> str:
return "Meow"
class Speaker(Protocol):
def speak(self) -> str: ...
def make_speak(thing: Speaker) -> str:
return thing.speak()
make_speak(Dog())
make_speak(Cat())
Defining Rich Protocols
Protocol can include properties, class methods, and operators:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Comparable(Protocol):
@property
def value(self) -> int: ...
def __lt__(self, other: "Comparable") -> bool: ...
class Score:
def __init__(self, points):
self.points = points
@property
def value(self) -> int:
return self.points
def __lt__(self, other):
return self.points < other.points
a = Score(10)
b = Score(20)
print(a < b)
print(isinstance(a, Comparable))
The @runtime_checkable decorator lets you use isinstance() with the Protocol at runtime.
See Also
typing— The typing module that provides Protocol- Python Type Hints — Guide to type hints in Python
- Abstract Base Classes — ABC approach for formal interfaces