Abstract Base Classes
Use abc.ABC to define a contract that subclasses must fulfil — enforced at instantiation time.
An abstract base class (ABC) defines a contract: it declares methods that must exist, but leaves the implementation to subclasses. Try to instantiate a class that has not implemented all the abstract methods and Python raises a TypeError immediately — no silent half-built objects.
The recipe
Inherit from abc.ABC and decorate the required methods with @abstractmethod.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
...
# Shape() -> TypeError: Can't instantiate abstract class
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
print(Square(4).area()) # 16Why bother?
ABCs give you fail-fast guarantees. If a teammate adds a new Shape subclass and forgets area(), they find out the moment they try to create one — not three modules later when something calls the missing method. You can also mix in concrete helper methods that build on the abstract ones.
Example
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
"""Every processor must know how to charge; refund is optional-with-default."""
@abstractmethod
def charge(self, amount_cents: int) -> str:
...
# A concrete method built on top of the abstract one
def charge_with_receipt(self, amount_cents: int) -> str:
ref = self.charge(amount_cents)
return f'Charged ${amount_cents/100:.2f} [ref {ref}]'
class StripeProcessor(PaymentProcessor):
def charge(self, amount_cents: int) -> str:
return f'stripe_{amount_cents}'
class BrokenProcessor(PaymentProcessor):
pass # forgot to implement charge()
print(StripeProcessor().charge_with_receipt(2599))
try:
BrokenProcessor() # fails at construction, not at call time
except TypeError as e:
print('blocked:', str(e).split(' with')[0])
print('is a subclass?', issubclass(StripeProcessor, PaymentProcessor))
# Output:
# Charged $25.99 [ref stripe_2599]
# blocked: Can't instantiate abstract class BrokenProcessor
# is a subclass? TrueWhen to use it
- A plugin system defines an abstract Exporter base class so all plugins must implement export().
- A storage layer defines an abstract Repository with find() and save() that SQL and Redis backends implement.
- A payment gateway enforces that every provider class implements charge() and refund() or fails at instantiation.
More examples
Define and implement an ABC
Defines an abstract Shape class; instantiating it directly fails, but Square implements the required area().
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
class Square(Shape):
def __init__(self, side): self.side = side
def area(self): return self.side ** 2
print(Square(4).area()) # 16
# Shape() -> TypeError: Can't instantiate abstract classMultiple abstract methods
Shows a Repository ABC with two abstract methods that the InMemoryRepo must implement.
from abc import ABC, abstractmethod
class Repository(ABC):
@abstractmethod
def find(self, id: int): ...
@abstractmethod
def save(self, obj) -> None: ...
class InMemoryRepo(Repository):
def __init__(self): self._store = {}
def find(self, id): return self._store.get(id)
def save(self, obj): self._store[obj['id']] = obj
repo = InMemoryRepo()
repo.save({'id': 1, 'name': 'Alice'})
print(repo.find(1))isinstance check with ABC
Demonstrates that isinstance() returns True for the ABC, enabling type-safe dispatch.
from abc import ABC, abstractmethod
class Serialisable(ABC):
@abstractmethod
def to_dict(self) -> dict: ...
class User(Serialisable):
def __init__(self, name): self.name = name
def to_dict(self): return {'name': self.name}
u = User('Bob')
print(isinstance(u, Serialisable)) # True
print(u.to_dict()) # {'name': 'Bob'}
Discussion