In Python, functionalities are not passed down through inheritance; they are acquired by implementing special methods. __len__ is what makes len() work on an object, __getitem__ is what makes indexing, slicing and the in operator work, and so on. This style of composing capabilities by satisfying the protocols the runtime already understands is central to Python’s design. Ruby, Go and Rust, among many other modern languages, prefer the same approach.
On the word "composition"
“Composition” here means composing capabilities by implementing the right special methods, not the OOP design pattern of composition over inheritance (delegating to held objects rather than subclassing). Both can coexist - this note is about the former.
This approach is called structural or protocol-based polymorphism, with duck typing at its core. It contrasts with nominal or inheritance-based polymorphism, the style adopted by Java, C# and C++. In nominal systems, “interfaces” live in named types and capabilities flow down a type tree; an object only counts as a Comparable if its class explicitly declares so. The structural side has a more piecemeal feel: an object counts as Sized because it happens to implement __len__, regardless of where it sits in any class hierarchy.
The two also differ in when contracts are settled. Nominal systems tend to declare contracts upfront, with the implementing types planned at design time. Structural systems do it retrospectively - any class can be retrofitted into a protocol later, simply by gaining the right method.
A useful pair of questions to tell the two apart:
- Where do the contracts live? In named types (nominal) or in the methods an object happens to implement (structural).
- How and when are they acquired and fulfilled? Declared at design time (nominal) or accumulated piece by piece, often after the fact (structural).
Example: collections.abc
For the protocols defined in collections.abc, Python does not require concrete classes to actually inherit from any of those ABCs (this is documented in Fluent Python 2e). Any class that implements the right special methods satisfies the protocol implicitly.
Contrast a nominal subclass:
class MyList(list): # nominal: inherit to gain `len()`
pass
len(MyList([1, 2, 3])) # 3with a structural duck:
class Bag: # structural: implement the protocol
def __init__(self, items):
self._items = list(items)
def __len__(self):
return len(self._items)
len(Bag([1, 2, 3])) # 3Bag has no relationship at all to collections.abc.Sized in the class tree, yet len() works on it.
Static checking via typing.Protocol
Structural typing was originally a runtime affair in Python, with isinstance() checks against ABCs as the only nod to formality. PEP 544 added typing.Protocol, a way to express structural contracts that static type checkers (e.g. mypy, pyright) can verify ahead of time.
from typing import Protocol
class Sized(Protocol):
def __len__(self) -> int: ...
def report(obj: Sized) -> str:
return f"size is {len(obj)}"
report(Bag([1, 2, 3])) # ok, no inheritance neededThis gives you the documentation and tooling benefits of nominal interfaces without forcing the implementing types into a hierarchy.
Relation to the data model
Structural polymorphism is a direct consequence of Python’s data model. The runtime defines a set of protocols (special methods); any object that implements them participates in the language as a first-class citizen. The idiomatic style is therefore not “subclass the built-in type” but “implement the special methods the built-in operations dispatch on”.