This lesson digs into the abc module itself: the building blocks (ABC, ABCMeta, abstractmethod), how @abstractmethod composes with @property, @classmethod, and @staticmethod, what virtual subclasses with register do, and which built-in abstract base classes ship in collections.abc. The chapter ends with guidance on when to pick each piece, and why a Protocol sometimes fits where an ABC doesn't.
The abc module exports a small set of names. Three of them do almost all the work:
| Name | What it is | Purpose |
|---|---|---|
ABCMeta | A metaclass | Enforces abstractness; rarely used directly |
ABC | A helper class with metaclass=ABCMeta | The modern, readable way to make a class abstract |
abstractmethod | A decorator | Marks a method as abstract |
ABCMeta is the engine. It tracks which methods on a class are abstract, and intercepts instantiation to raise TypeError when any are still unimplemented. ABC is a one-line helper that wires the metaclass in, so the syntax becomes class Foo(ABC) instead of class Foo(metaclass=ABCMeta). abstractmethod is the marker that tells ABCMeta "subclasses must override this".
Both versions produce identical behavior. The ABC form is what almost every codebase written today uses; metaclass=ABCMeta is mainly useful when a class already needs a custom metaclass for some other reason and abstractness has to combine with it.
The abstractmethod decorator is a marker. It sets __isabstractmethod__ = True on the function. ABCMeta reads that flag and acts on it. Applying @abstractmethod to a class that doesn't have ABCMeta in its metaclass chain makes the decorator documentation with no enforcement:
The pattern is always ABC plus abstractmethod, not one or the other. Pair them, or the decorator has no effect.
ABCMeta Enforces AbstractnessWhen Python builds a class, the metaclass's __init__ runs. ABCMeta.__init__ walks the class body, finds every method marked with __isabstractmethod__, and stores their names in a frozen set on the class as __abstractmethods__. When the class is called to construct an instance, ABCMeta.__call__ checks whether that set is empty. If it isn't, the result is the TypeError that lists the missing methods.
That frozen set is the registry the metaclass consults. A subclass inherits the parent's abstract methods and removes from the set any that it overrides. Once the set is empty, the subclass is concrete.
CreditCardProcessor overrode both abstract methods, so its __abstractmethods__ set is empty, and instantiation works. Overriding only one leaves the other in the set, and instantiation fails:
The error message names the class and the methods that are still abstract. That message is generated from the __abstractmethods__ set; it's not a hand-written check.
The abstractness check happens once per class definition (to build the set) and once per instantiation (to verify the set is empty). The runtime cost is negligible; the only real cost is that abstract classes can't be used as plain placeholders.
@abstractmethod With Other Decoratorsabstractmethod is the only decorator the abc module exposes for marking methods, but it composes with the built-in property, classmethod, and staticmethod decorators. The combinations look a little odd at first because decorator order matters in a way that doesn't usually come up.
@abstractmethod With @propertyA common contract is "every implementation must expose this value as a read-only attribute". Place @property on top and @abstractmethod underneath:
Decorators apply bottom-up. @abstractmethod runs first and marks the raw function as abstract. @property wraps the abstract function as a property descriptor. The result is a property whose getter is tracked in the abstract-method set, which is what ABCMeta needs to enforce.
Reversing the order breaks the enforcement. @property wraps the function first, producing a property object. Then @abstractmethod marks that property object, but ABCMeta doesn't propagate the abstract flag correctly through a property wrapper unless @abstractmethod was applied to the raw function. Subclasses can then skip the override without Python flagging the omission.
@abstractmethod With @classmethodA factory method that every subclass must provide. The decorator order is @classmethod on top, @abstractmethod underneath:
The same bottom-up rule applies: @abstractmethod marks the function, @classmethod wraps it as a class method. Subclasses must define their own from_csv_row classmethod, and the parent's abstract version blocks instantiation until they do.
@abstractmethod With @staticmethodA static helper that every implementation must provide. Same pattern:
@staticmethod doesn't receive self or cls, so the abstract version is a function-shaped contract attached to the class. The bottom-up decorator order still applies.
| Combination | Order (top to bottom) | What subclasses must provide |
|---|---|---|
| Abstract method | @abstractmethod | A method with the same name |
| Abstract property | @property, then @abstractmethod | A @property with the same name |
| Abstract classmethod | @classmethod, then @abstractmethod | A @classmethod with the same name |
| Abstract staticmethod | @staticmethod, then @abstractmethod | A @staticmethod with the same name |
The rule of thumb: @abstractmethod goes on the bottom, the role-defining decorator goes on top. Any other order either breaks enforcement or produces a confusing object.
ABCMeta.registerABCMeta has one capability that turns abstract base classes from a "subclasses must inherit from me" mechanism into something more flexible: virtual subclassing. Calling Base.register(SomeClass) tells the ABC machinery to treat SomeClass as a subclass of Base for the purposes of isinstance and issubclass, without modifying SomeClass and without any actual inheritance.
StdlibLogger does not inherit from Loggable in the normal sense. It has no Loggable in its __mro__:
But isinstance(logger, Loggable) returns True anyway. The ABC machinery keeps a private set of registered subclasses on each abstract base, and isinstance consults that set in addition to the normal inheritance chain. The registered class itself is unchanged.
This is useful in three situations:
register accepts it without a wrapper.list, dict, and tuple are registered as virtual subclasses of the abstract bases in collections.abc. That's how isinstance([], collections.abc.Iterable) returns True even though list doesn't inherit from Iterable directly.The catch is real: register does not check that the class has the required methods. It's a promise to Python, not a verification. If StdlibLogger were missing log, the registration would still succeed, and isinstance would still return True. The error would only show up at call time, the same way it does with plain duck typing. For verified shape checks, combine register with tests or use typing.Protocol with @runtime_checkable.
The diagram shows where virtual subclasses fit. isinstance doesn't only walk the inheritance chain; it also asks the ABC whether the class has been registered. Either route returns True.
register doesn't enforce method presence. The check happens whenever the method is called, which means a missing or misnamed method on a registered class fails at call time, not at registration. Pair register with tests when correctness matters.
collections.abcPython ships with a set of pre-defined abstract base classes that describe the protocols built into the language. They live in collections.abc, and they're the standard way to type-check or assert "this object behaves like a sequence" or "this object is iterable."
The most common ones:
| ABC | Required methods | Examples that satisfy it |
|---|---|---|
Iterable | __iter__ | list, tuple, dict, set, generators |
Iterator | __iter__, __next__ | Generators, file objects, iter(some_list) |
Sized | __len__ | list, tuple, dict, set, str |
Container | __contains__ | list, tuple, dict, set, str |
Sequence | __getitem__, __len__ | list, tuple, str |
MutableSequence | Sequence plus __setitem__, __delitem__, insert | list |
Mapping | __getitem__, __len__, __iter__, plus methods like keys, values, items | dict |
MutableMapping | Mapping plus __setitem__, __delitem__ | dict |
Set | __contains__, __iter__, __len__ | set, frozenset |
Hashable | __hash__ | Most immutable types |
Callable | __call__ | Functions, lambdas, classes with __call__ |
Two things make these useful. First, they support structural checks:
The built-in types are all registered as virtual subclasses of the appropriate ABCs, so isinstance works without any setup. Custom classes that implement the right methods don't automatically register; they have to inherit from the ABC or call register explicitly. (Some ABCs detect their own protocols through __subclasshook__, which lets isinstance succeed structurally for classes that have the right methods. Iterable does this for __iter__, for example.)
Second, inheriting from them brings mixin methods at no extra cost. A class that inherits from collections.abc.Sequence and implements __getitem__ and __len__ gets __iter__, __contains__, index, count, __reversed__, and __add__ automatically:
WishList implemented only two methods but got six more methods from the Sequence ABC. This is the second job collections.abc does: it standardizes what a "sequence" or a "mapping" or a "set" offers, so any class that fits the protocol picks up the common helpers without rewriting them.
The same pattern applies to MutableSequence (add __setitem__, __delitem__, and insert; pick up append, extend, pop, remove, reverse, __iadd__ automatically), Mapping (pick up __contains__, keys, items, values, get, __eq__, __ne__), and others. The list of provided mixin methods for each ABC is in the official collections.abc documentation.
| Implemented methods | Inherit from | Methods inherited |
|---|---|---|
__iter__ | Iterable | __iter__ is the only thing in the protocol |
__getitem__, __len__ | Sequence | __contains__, __iter__, __reversed__, index, count |
__getitem__, __setitem__, __delitem__, __len__, insert | MutableSequence | All the above plus append, extend, pop, remove, reverse, __iadd__ |
__getitem__, __len__, __iter__ | Mapping | __contains__, keys, items, values, get, __eq__, __ne__ |
__getitem__, __setitem__, __delitem__, __iter__, __len__ | MutableMapping | All the above plus pop, popitem, clear, update, setdefault |
Inheriting from collections.abc.Sequence instead of building a list-like class from scratch saves work and signals intent. A reader of the code knows that WishList is meant to behave like a sequence, and a type checker knows the same thing.
abc.ABC vs typing.ProtocolPython has two ways to describe "an object with these methods". abc.ABC is one. typing.Protocol (added in Python 3.8) is the other. This section makes the trade-off concrete with the abc machinery in mind.
ABC is nominal: a class belongs to a type because it inherits from that type (directly or via register). The relationship is explicit. Instantiation is checked at runtime. Omitting an abstract method causes TypeError the moment an instance of the offending class is created.
Protocol is structural: a class belongs to a type because it has the right methods, regardless of inheritance. The relationship is implicit. Static type checkers verify the shape at type-check time. There's no runtime enforcement by default; @runtime_checkable adds an isinstance check that inspects method names only.
Both calls work. EmailNotifier explicitly inherits from Notifier; StandaloneNotifier doesn't inherit from anything but still satisfies NotifierProtocol because it has a send method. A type checker accepts both in the call to notify.
The trade-off in a table:
| Aspect | abc.ABC | typing.Protocol |
|---|---|---|
| Style | Nominal (explicit inheritance or register) | Structural (shape-based) |
| Enforced at | Runtime, on instantiation | Type-check time (mypy, pyright) |
Runtime isinstance | Works automatically | Only with @runtime_checkable, names only |
| Missing method | TypeError at instantiation | Type-check warning |
| Inheritance | Required (or register) | Not required |
| Best fit | Library authors locking down a contract | API surfaces that accept any matching shape |
Three rules of thumb cover most cases:
ABC.Protocol, optionally with @runtime_checkable.ABC for the inheritance side and Protocol for the type hints. They coexist fine.The full reference for ABC is at docs.python.org under the abc module.
abc shows up in a few well-defined patterns. Each one solves a specific problem and provides a template that's easy to recognize in other code.
An abstract base defines a single operation; multiple concrete implementations provide different algorithms for that operation. Callers hold a reference typed as the abstract base and pick which strategy to use at runtime:
DiscountStrategy declares the contract: any discount has an apply(price) method. PercentageDiscount and FlatDiscount are concrete strategies. checkout doesn't know or care which one it has; it calls apply. Adding a "buy one get one" strategy means writing one new class.
An abstract base provides a concrete method that orchestrates the work and delegates specific steps to abstract methods that subclasses must implement. Subclasses fill in the specifics; the parent owns the overall flow:
process is the template; it defines the flow. validate and charge are the holes that subclasses fill. confirm is a concrete method shared by all subclasses. The pattern guarantees that every implementation runs the same sequence; the variation is in the individual steps.
Combined with register, an abstract base can act as a hub that knows about a set of related implementations. Useful for plugin systems or extensible APIs:
CsvLoader and JsonLoader are pre-existing classes that fit the FileLoader shape but weren't designed to inherit from anything. Registering them turns them into virtual subclasses, and code elsewhere can do isinstance(obj, FileLoader) to confirm membership. The classes themselves are unchanged.
| Pattern | What it provides | When to use it |
|---|---|---|
| Strategy | Swap algorithms behind one interface | Multiple implementations of "do X" |
| Template Method | Fixed flow with customizable steps | Same orchestration, different specifics |
| Registry of Implementations | Group existing classes under one ABC | Plugin systems, third-party integration |
These aren't the only patterns abc enables, but they cover the situations that come up most often. Each one uses different parts of the abc machinery: Strategy and Template Method rely on inheritance and @abstractmethod; Registry of Implementations uses register to fold in classes from outside the codebase.
10 quizzes