Abstract Base Classes in Python: The abc Module Explained
- Subclassing ABC and decorating a method with @abstractmethod makes that method mandatory for any concrete subclass.
- Python refuses to instantiate a subclass that hasn't implemented every abstract method, raising TypeError at object-creation time.
- This is enforced earlier and more reliably than the older convention of raising NotImplementedError inside a base method's body.
- ABCs formalise a required interface; they don't replace duck typing everywhere it already works fine.
The problem: a base class that promises what it doesn't enforce
A common pattern before abc was to define a base class with methods that just raise an error, expecting subclasses to override them:
class Shape:
def area(self):
raise NotImplementedError
class Circle(Shape):
pass # forgot to implement area()
c = Circle()
c.area() # only fails here, at call time, possibly much later
This works, but the mistake only surfaces when area() is actually called — which might be long after the object was created, deep in some other part of the program, far from where the missing implementation actually is. Nothing stops Circle() itself from succeeding.
Defining an ABC with abstractmethod
The abc module makes the requirement explicit and enforces it earlier. Inheriting from ABC and marking a method with @abstractmethod tells Python that any concrete subclass must override it:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
...
@abstractmethod
def perimeter(self):
...
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
Circle implements both abstract methods, so it can be instantiated normally. The ... (or a docstring) in the abstract method body never actually runs — it exists purely to document the expected signature.
Why you can't instantiate an incomplete subclass
Leave one abstract method unimplemented, and Python refuses to create an instance at all, rather than waiting for the method to be called:
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
# perimeter() not implemented
s = Square(4)
# TypeError: Can't instantiate abstract class Square
# without an implementation for abstract method 'perimeter'
The error fires the moment Square(4) is evaluated, with a message naming exactly which method is missing. That's a meaningfully earlier and more specific failure than a NotImplementedError buried inside a method body somewhere a caller might not reach for months.
Abstract properties and other abstract members
@abstractmethod combines with other decorators, so a subclass can be required to provide a property, not just a plain method:
from abc import ABC, abstractmethod
class Shape(ABC):
@property
@abstractmethod
def name(self):
...
class Circle(Shape):
@property
def name(self):
return "circle"
The decorator order matters — @property goes on top, @abstractmethod directly above the function definition — so that Python recognises the whole thing as both a property and a required abstract member.
ABCs alongside duck typing, not instead of it
Python code has always leaned on duck typing: if an object has a read() method, code that calls read() generally doesn't care what class the object actually is. ABCs don't replace this — they formalise it for cases where a contract genuinely needs to be explicit and enforced. The standard library uses this pattern itself: collections.abc defines ABCs like Iterable, Sequence, and Mapping that describe the interfaces those protocols expect, and isinstance() checks against them work even for classes that never explicitly inherited from the ABC, as long as the required dunder methods are present. Using an ABC is a choice to trade some flexibility for an earlier, clearer error when a required piece is missing — worth it for a plugin system or a library's public extension points, often unnecessary for a one-off internal class hierarchy.
When an ABC is worth the ceremony
ABCs pay for themselves when several people or several subclasses are expected to implement the same interface, and a missing method should fail loudly and immediately rather than quietly at call time. A plugin architecture, a family of exporters that all need a save() method, or a set of strategy classes that all need to implement the same handful of operations are good fits. For a small, single-author codebase with one or two subclasses that you control directly, plain inheritance with clear documentation is often enough, and the extra abstraction doesn't buy much. The built-in ABCs for common protocols are catalogued in the collections.abc documentation.
A useful test when deciding is to imagine a new contributor adding a subclass six months from now without reading the original design notes. If skipping a required method would fail loudly with a specific, named error at the point of instantiation, an ABC has done its job. If the same mistake would instead surface later as a confusing AttributeError deep inside some unrelated code path, or worse, as a silent wrong answer, that's a strong signal the interface needed enforcing rather than trusting to convention. Teams that publish a class meant to be subclassed outside their own codebase — a plugin base class, a public library extension point — tend to lean on ABCs more heavily than teams writing purely internal code, precisely because they can't rely on a shared conversation to catch a missing method before it ships.