Abstract Base Classes
An Abstract Base Class (ABC) is a class that cannot be instantiated directly and instead declares an interface that concrete subclasses must fulfil. Python implements ABCs through the
abcmodule (infrastructure specified by PEP 3119), built on a metaclass,ABCMeta. Two mechanisms make ABCs powerful and unusual. First, marking a method@abstractmethodmakes any subclass that leaves it unimplemented non-instantiable — the interpreter raisesTypeErrorat construction time. Second, an ABC can recognize a class as a subtype without that class inheriting from it at all, either through explicitregister()(a virtual subclass) or through a__subclasshook__that inspects the class’s methods. Thecollections.abcsubmodule uses the latter to make duck typingisinstance()-checkable: any object with__iter__is anIterable, no inheritance required. ABCs are thus Python’s nominal + registration, runtime-enforced answer to “does this object satisfy interface X?”.
ABCs are one half of a contrast pair. The other half — Protocols and Structural Typing (typing.Protocol) — answers the same “does X satisfy interface Y?” question, but structurally (by shape, not declaration) and statically (checked by a type checker, not the interpreter). The full nominal-versus-structural comparison table and the decision rule for which to reach for live in that sibling note; this note covers the runtime ABC machinery in depth and recaps the contrast briefly at the end. In one line: ABCs are checked at runtime and require you to declare or register membership; Protocols are checked statically and recognize membership by shape.
Mental Model
Think of an ABC as a runtime interface contract enforced by a custom metaclass. The metaclass ABCMeta is the engine — recall that a metaclass is “the class of a class,” the thing that runs when a class statement executes and that controls how the resulting class behaves. ABCMeta does three things ordinary metaclasses do not. (1) At class-creation it scans for unimplemented @abstractmethods and records them in a frozenset __abstractmethods__; object.__new__ then refuses to instantiate any class whose __abstractmethods__ is non-empty. (2) It overrides __instancecheck__ and __subclasscheck__ so that isinstance() and issubclass() consult an extra registry and an optional hook, not just the inheritance graph. (3) It maintains a registry of virtual subclasses added by register(). The payoff is that “X is a Y” can be true for three different reasons: X inherits from Y, X was registered with Y, or Y’s __subclasshook__ looked at X’s methods and said yes.
flowchart TD A["isinstance(obj, MyABC)<br/>issubclass(C, MyABC)"] --> B["ABCMeta.__subclasscheck__<br/>(C-accelerated in _abc)"] B --> C{"1. positive<br/>cache hit?"} C -- yes --> Y["True"] C -- no --> D{"2. negative<br/>cache hit?"} D -- yes --> N["False"] D -- no --> E{"3. __subclasshook__<br/>returns?"} E -- "True/False" --> CACHE["cache + return"] E -- NotImplemented --> F{"4. real<br/>subclass?"} F -- yes --> Y F -- no --> G{"5. in registry,<br/>or subclass of<br/>a registered class?"} G -- yes --> Y G -- no --> H{"6. subclass of<br/>any subclass?"} H -- yes --> Y H -- no --> N
Diagram: the exact decision order inside _abc_subclasscheck (CPython Modules/_abc.c, v3.14.5). The insight: an ordinary issubclass only walks the inheritance graph (step 4 here); an ABC interposes a cache, a structural hook, and a registration registry around it. The hook (step 3) is what turns duck typing into an isinstance-able fact, and the two caches exist precisely because steps 3–6 are expensive to recompute on every call.
How @abstractmethod Blocks Instantiation
When the class statement for an ABC executes, ABCMeta.__new__ builds the class and then computes __abstractmethods__. The algorithm (visible in Lib/abc.py, and recomputable later via abc.update_abstractmethods): take the union of all __abstractmethods__ inherited from base classes, remove any name that this class now provides a concrete implementation for, and add any name in this class’s own namespace whose value has __isabstractmethod__ == True. The result is frozen into cls.__abstractmethods__ = frozenset(abstracts) (per Lib/abc.py).
The @abstractmethod decorator does almost nothing on its own: it simply sets func.__isabstractmethod__ = True on the function object. The real enforcement lives in object.__new__, which checks whether the class being instantiated has a non-empty __abstractmethods__ and, if so, raises TypeError: Can't instantiate abstract class C with abstract method(s) m.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
class Circle(Shape):
def area(self) -> float: # supplies the abstract method
return 3.14159
Shape() # TypeError: Can't instantiate abstract class Shape ...
Circle() # OK — __abstractmethods__ is now emptyclass Shape(ABC) — inheriting from abc.ABC is the ergonomic way to get ABCMeta as the metaclass; ABC is a trivial helper whose own metaclass is ABCMeta (added in 3.4, per the abc docs). The equivalent explicit form is class Shape(metaclass=ABCMeta). @abstractmethod def area marks area; Shape.__abstractmethods__ becomes frozenset({'area'}). Shape() fails because that frozenset is non-empty. Circle overrides area, so its __abstractmethods__ is empty and Circle() succeeds.
PEP 3119 deliberately made abstract methods less restrictive than Java’s: “these abstract methods may have an implementation” callable via super(), which is useful as the terminating end-point of cooperative-multiple-inheritance super-calls (per the abc docs). To stack @abstractmethod with classmethod/staticmethod/property, apply @abstractmethod as the innermost decorator:
class C(ABC):
@classmethod
@abstractmethod
def factory(cls, arg): ... # abstract classmethod
@property
@abstractmethod
def name(self) -> str: ... # abstract propertyThe order matters because abstractmethod must run first to set __isabstractmethod__ on the underlying function, and the outer descriptor (classmethod, property) is then responsible for propagating that flag — modern classmethod/staticmethod/property are “correctly identified as abstract when applied to an abstract method,” which is why the standalone abstractclassmethod, abstractstaticmethod, and abstractproperty decorators have been deprecated since 3.3 (per the abc docs). A custom descriptor must expose its own __isabstractmethod__ property returning True if any composed function is abstract.
If you implement an abstract method after the class is created (e.g. via a decorator or monkey-patching), __abstractmethods__ is stale; call abc.update_abstractmethods(cls) (added in 3.10) to recompute it. It does nothing if cls is not an ABCMeta instance, and it does not propagate to subclasses (per the abc docs).
Virtual Subclasses via register()
The second mechanism breaks the link between being a subtype and inheriting. ABCMeta.register(subclass) records subclass as a virtual subclass of the ABC. Thereafter issubclass(subclass, ABC) and isinstance(instance_of_subclass, ABC) return True — but, crucially, the ABC does not appear in the subclass’s MRO (method resolution order), and none of the ABC’s method implementations become callable on the subclass, “not even via super()” (per the abc docs). Registration is a pure type-membership assertion; it transfers no behavior.
from abc import ABC
class MyABC(ABC):
pass
MyABC.register(tuple) # tuple now a virtual subclass
assert issubclass(tuple, MyABC) # True
assert isinstance((), MyABC) # True
assert MyABC not in tuple.__mro__ # registration ≠ inheritanceMyABC.register(tuple) makes the built-in tuple a virtual subclass of a class tuple’s author never heard of — this is the power of registration: it works on classes you do not own, including built-ins. register() returns its argument (since 3.3), so it can also be used as a class decorator: @MyABC.register above a class statement. Note @abstractmethod enforcement does not apply to virtual subclasses — they were never checked for abstract methods, because they do not inherit (per the abc docs).
__subclasshook__ — Duck Typing Made isinstance-able
The third mechanism is the most interesting, and it is what collections.abc is built on. ABCMeta.__subclasscheck__ calls a classmethod __subclasshook__(cls, C) and, based on its return value, decides membership before falling back to the registry or the inheritance graph. The hook returns True (C is a subclass), False (C is definitively not, short-circuiting everything else), or NotImplemented (carry on with the normal mechanism) (per the abc docs). By inspecting C’s method resolution order for the presence of methods, a __subclasshook__ can accept any class with the right shape — runtime structural recognition, without registration.
from abc import ABC, abstractmethod
class MyIterable(ABC):
@abstractmethod
def __iter__(self):
while False:
yield None
@classmethod
def __subclasshook__(cls, C):
if cls is MyIterable:
if any("__iter__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented__subclasshook__ is a classmethod. The guard if cls is MyIterable ensures the hook only fires for MyIterable itself, not for nominal subclasses that may add further requirements. The test any("__iter__" in B.__dict__ for B in C.__mro__) walks C’s MRO looking for a class that defines __iter__ in its own namespace — if found, return True; otherwise NotImplemented so the normal check proceeds. This is exactly the pattern collections.abc.Iterable uses, which is why isinstance(some_generator, Iterable) is True with no registration.
The collections.abc Hierarchy
The collections.abc submodule (added in 3.3) provides ready-made ABCs “for testing whether a class provides a particular interface” (per the collections.abc docs). They form a hierarchy whose roots are one-method abstractions and whose leaves are rich interfaces with many mixin methods:
| ABC | Inherits | Abstract methods | Notable mixin methods |
|---|---|---|---|
Container | — | __contains__ | — |
Hashable | — | __hash__ | — |
Iterable | — | __iter__ | — |
Iterator | Iterable | __next__ | __iter__ |
Sized | — | __len__ | — |
Callable | — | __call__ | — |
Collection | Sized, Iterable, Container | __contains__, __iter__, __len__ | — |
Sequence | Reversible, Collection | __getitem__, __len__ | __contains__, __iter__, __reversed__, index, count |
MutableSequence | Sequence | + __setitem__, __delitem__, insert | append, pop, remove, reverse, extend, __iadd__ |
Set | Collection | __contains__, __iter__, __len__ | __le__ … __ge__, __and__, __or__, __sub__, __xor__, isdisjoint |
Mapping | Collection | __getitem__, __iter__, __len__ | __contains__, keys, items, values, get, __eq__, __ne__ |
MutableMapping | Mapping | + __setitem__, __delitem__ | pop, popitem, clear, update, setdefault |
(Version-added facts confirmed against the 3.14 page: Generator/Awaitable/Coroutine/AsyncIterable/AsyncIterator added in 3.5; Collection/Reversible/AsyncGenerator added in 3.6; Buffer added in 3.12 per PEP 688, per the collections.abc docs.) The deep mechanics of Sequence/Mapping themselves live in The Sequence and Mapping Protocols and the iteration ABCs in The Iterator Protocol; this note’s concern is the ABC machinery common to all of them.
Two distinct uses follow from the table. As a mix-in, inheriting from Set and supplying the three abstract methods gets you &, |, -, ^ and isdisjoint for free — the mixin methods are real implementations defined in terms of the abstract ones. As an interface test, isinstance(x, Sized) answers “can I call len(x)?” — but only for the ABCs that override __subclasshook__ (see the critical caveat below).
The mix-in story has a sharp edge worth knowing: the Set and MutableSet mixins construct new result objects by calling a classmethod _from_iterable(cls, it), whose default implementation is simply return cls(it) (verified in Lib/_collections_abc.py). That default only works if your subclass’s constructor accepts a single iterable argument. If your __init__ has a different signature — say it takes a name plus elements — then s1 & s2 will blow up inside the mixin, because the mixin tries to do YourSet(<generator>). The fix the docs prescribe is to override _from_iterable (per the collections.abc docs). This is the canonical example of a subtlety that ABCs-as-mixins introduce and protocols never do: a protocol gives you no implementations, so there is no hidden constructor contract to satisfy.
A second mechanical detail closes the loop on how isinstance reaches all this. ABCMeta overrides __instancecheck__, and the C implementation (_abc__abc_instancecheck_impl in Modules/_abc.c) does not simply call type(instance) — it reads instance.__class__ and checks subclass membership of that. This __class__-based path is why an object can lie about its type (by setting __class__) and still be recognized, and why instance checks ultimately funnel into the same _abc_subclasscheck decision tree described above, with the same cache. The instance check first tries the object’s real type and its declared __class__, short-circuiting to True the moment either is a confirmed subclass.
A Critical Misunderstanding: Not All collections.abc ABCs Duck-Type
This is the sharpest and most-missed point. Only the one-trick-pony ABCs override __subclasshook__ to recognize a class structurally: Hashable, Iterable, Iterator, Reversible, Generator, Sized, Callable, Collection, Container, Awaitable, Coroutine, the async iterators, and Buffer. For these, “a class with __iter__ is an Iterable” holds automatically. But the rich ABCs — Sequence, MutableSequence, Mapping, MutableMapping, Set, MutableSet — do not override __subclasshook__. Structural recognition “only works for simple interfaces; more complex interfaces require registration or direct subclassing” (per the collections.abc docs).
import collections.abc as cabc
class Ducky:
def __getitem__(self, i): return i
def __len__(self): return 0
isinstance(Ducky(), cabc.Sized) # True — Sized has a __subclasshook__
isinstance(Ducky(), cabc.Sequence) # False — Sequence does NOTDucky has __getitem__ and __len__, so it walks and quacks like a sequence — yet isinstance(Ducky(), Sequence) is False, because Sequence has no __subclasshook__ and Ducky neither inherits from nor is registered with it. The reason a hook is impossible here is genuine ambiguity: both Sequence and Mapping require __getitem__ and __len__, so method presence alone cannot disambiguate seq[0] (integer index) from mapping[key] (key lookup). People routinely assume isinstance(x, Sequence) duck-types; it does not. To make Ducky a Sequence, inherit (class Ducky(cabc.Sequence)) or register (cabc.Sequence.register(Ducky)).
The C Accelerator and the Cache
isinstance/issubclass against ABCs sit on hot paths, so the membership logic is implemented in C in Modules/_abc.c, with a pure-Python fallback in Lib/_py_abc.py. Lib/abc.py chooses at import time (per Lib/abc.py):
try:
from _abc import (get_cache_token, _abc_init, _abc_register,
_abc_instancecheck, _abc_subclasscheck, ...)
except ImportError:
from _py_abc import ABCMeta, get_cache_tokenEach ABC carries an _abc_data struct with three cache fields (verified verbatim in Modules/_abc.c): _abc_registry (the weak set of registered virtual subclasses), _abc_cache (positive results — confirmed subclasses), and _abc_negative_cache (confirmed non-subclasses), plus _abc_negative_cache_version. The negative cache is the subtle one: registering a new class anywhere can turn a previously-False answer into True, so a global abc_invalidation_counter is incremented on every register(). When _abc_subclasscheck runs, it compares the ABC’s _abc_negative_cache_version against the global counter; if stale, it clears the negative cache before consulting it. abc.get_cache_token() (added in 3.4) exposes this counter as an opaque token so caching consumers can detect that the world changed.
The exact decision order in _abc_subclasscheck (the numbered comments are from the source) is: (1) check the positive _abc_cache → return True on hit; (2) check/invalidate the negative cache → return False on hit; (3) call __subclasshook__ → if it returns True/False, cache and return; if NotImplemented, continue; (4) PyType_IsSubtype — a genuine inheritance subclass → True; (5) check the registry (recursively — a subclass of a registered class also counts); (6) check whether it is a subclass of any of the ABC’s own subclasses; otherwise cache negative and return False (per Modules/_abc.c). The diagram above mirrors this order exactly. The performance implication: the first isinstance against an ABC may run the full hook + registry walk, but the result is cached on a per-ABC weak set, so steady-state checks are near-free until the next register() invalidates negatives.
Nominal vs Structural — Brief Recap
ABCs decide membership nominally (you inherit or register()) and enforce it at runtime (in the metaclass’s __instancecheck__/__subclasscheck__). Protocols decide membership structurally (by shape) and check it statically (in a type checker). The one-trick-pony collections.abc ABCs blur the line by doing a structural check at runtime via __subclasshook__ — and PEP 544 explicitly notes that protocols “formalize for static type checking” exactly what those ABCs “already provide … via __subclasshook__” at runtime (per PEP 544). Reach for an ABC when you need a runtime guarantee, shared mix-in implementations, or to extend the collections.abc ecosystem; reach for a Protocol for static, non-intrusive interface checking across code you do not own. The full comparison table and decision rule live in Protocols and Structural Typing.
Production Notes
The standard library itself is the best worked example: collections.OrderedDict, Counter, and defaultdict are concrete classes built atop MutableMapping; the numbers module (PEP 3141) defines a numeric ABC tower (Number → Complex → Real → Rational → Integral) that int and float are registered with rather than inheriting from. A common production pitfall is using ABCs for isinstance gatekeeping on hot paths and forgetting the negative-cache invalidation cost when code calls register() dynamically — every register() invalidates all ABCs’ negative caches globally, so heavy dynamic registration in a tight loop can quietly defeat the cache. A second pitfall, noted above with PEP 688: collections.abc.ByteString is deprecated since 3.12 and will be removed in 3.17 (it had no methods and never recognized memoryview, so being an instance of it told you nothing useful), and code should use isinstance(obj, collections.abc.Buffer) for runtime buffer-protocol checks or bytes | bytearray | memoryview unions in annotations — the deprecation/removal versions are stated verbatim on the collections.abc docs.
See Also
- Protocols and Structural Typing — the structural, static-checking sibling; the other half of this contrast pair (full comparison table lives there)
- Metaclasses —
ABCMetais a metaclass; this explains the machinery it builds on - The type and object Relationship — MRO and the inheritance graph that
register()deliberately sidesteps - The Sequence and Mapping Protocols — the rich
collections.abcABCs that do not duck-type - The Iterator Protocol —
Iterable/Iterator, the canonical__subclasshook__ABCs - Dunder Methods and the Data Model — the methods
collections.abcABCs test for - §13 of Python Internals MOC — The Type System and Data-Model Protocols