AlgoMaster Logo

abc Module

Medium Priority17 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

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 Three Building Blocks

The abc module exports a small set of names. Three of them do almost all the work:

NameWhat it isPurpose
ABCMetaA metaclassEnforces abstractness; rarely used directly
ABCA helper class with metaclass=ABCMetaThe modern, readable way to make a class abstract
abstractmethodA decoratorMarks 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.

How ABCMeta Enforces Abstractness

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

Combining @abstractmethod With Other Decorators

abstractmethod 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 @property

A 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 @classmethod

A 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 @staticmethod

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

CombinationOrder (top to bottom)What subclasses must provide
Abstract method@abstractmethodA method with the same name
Abstract property@property, then @abstractmethodA @property with the same name
Abstract classmethod@classmethod, then @abstractmethodA @classmethod with the same name
Abstract staticmethod@staticmethod, then @abstractmethodA @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.

Virtual Subclasses With ABCMeta.register

ABCMeta 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:

  • Integrating third-party classes. A library outside the codebase has the right shape but doesn't inherit from a local abstract base. register accepts it without a wrapper.
  • Legacy code. An existing class hierarchy predates the new abstract base. The abstraction layers on top without changing the existing classes.
  • Built-in protocols. Python's own built-ins like 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.

Built-in Abstract Base Classes: collections.abc

Python 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:

ABCRequired methodsExamples 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
MutableSequenceSequence plus __setitem__, __delitem__, insertlist
Mapping__getitem__, __len__, __iter__, plus methods like keys, values, itemsdict
MutableMappingMapping 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 methodsInherit fromMethods inherited
__iter__Iterable__iter__ is the only thing in the protocol
__getitem__, __len__Sequence__contains__, __iter__, __reversed__, index, count
__getitem__, __setitem__, __delitem__, __len__, insertMutableSequenceAll 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__MutableMappingAll 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.

When to Use abc.ABC vs typing.Protocol

Python 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:

Aspectabc.ABCtyping.Protocol
StyleNominal (explicit inheritance or register)Structural (shape-based)
Enforced atRuntime, on instantiationType-check time (mypy, pyright)
Runtime isinstanceWorks automaticallyOnly with @runtime_checkable, names only
Missing methodTypeError at instantiationType-check warning
InheritanceRequired (or register)Not required
Best fitLibrary authors locking down a contractAPI surfaces that accept any matching shape

Three rules of thumb cover most cases:

  1. The codebase controls the subclasses and needs a runtime guarantee that every implementation is complete. Use ABC.
  2. Third-party or legacy classes need to satisfy an interface without modification. Use Protocol, optionally with @runtime_checkable.
  3. Both apply: a published contract for explicit subclasses and structural typing for everything else. Use 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.

Real-World Patterns

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.

Pattern 1: Strategy

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.

Pattern 2: Template Method

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.

Pattern 3: Registry of Implementations

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.

PatternWhat it providesWhen to use it
StrategySwap algorithms behind one interfaceMultiple implementations of "do X"
Template MethodFixed flow with customizable stepsSame orchestration, different specifics
Registry of ImplementationsGroup existing classes under one ABCPlugin 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.

Quiz

abc Module Quiz

10 quizzes