Object-oriented projects often need a common contract for several implementations. A payment class must expose process(); a repository needs save() and get(); a plugin must declare required lifecycle methods. The Python abc module provides abstract base classes, mandatory methods, and virtual subclasses for expressing these contracts at runtime.
This guide covers ABC, ABCMeta, abstractmethod, register(), __subclasshook__(), and update_abstractmethods(). It complements our articles about Python types, inspect, singledispatch, descriptors, and dataclasses.
What an abstract class is
An abstract class describes expected behavior and may provide partial implementation, but it cannot be instantiated while unresolved abstract members remain.
from abc import ABC, abstractmethod
class Storage(ABC):
@abstractmethod
def save(self, key, value):
pass
@abstractmethod
def get(self, key):
passCalling Storage() raises TypeError. A concrete subclass must implement every abstract member.
Create a concrete implementation
class MemoryStorage(Storage):
def __init__(self):
self._data = {}
def save(self, key, value):
self._data[key] = value
def get(self, key):
return self._data[key]
repository = MemoryStorage()
repository.save("user", "Ana")
print(repository.get("user"))The base class documents the contract and prevents accidental construction of incomplete implementations.
ABC is a helper class
ABC uses ABCMeta as its metaclass. Inheriting from ABC is the simplest way to declare an abstract base class.
A class that already needs another metaclass can encounter a conflict. Derive a compatible metaclass from ABCMeta or reconsider a complex inheritance design.
abstractmethod
@abstractmethod marks required behavior. It must be the innermost decorator when combined with classmethod, staticmethod, or property.
class Factory(ABC):
@classmethod
@abstractmethod
def create(cls, configuration):
pass
@staticmethod
@abstractmethod
def validate(configuration):
passThe order matters because abstractmethod marks the underlying descriptor.
Abstract properties
class Document(ABC):
@property
@abstractmethod
def title(self):
passA subclass implements another property.
class Report(Document):
def __init__(self, title):
self._title = title
@property
def title(self):
return self._titleOlder helpers such as abstractproperty, abstractclassmethod, and abstractstaticmethod remain for compatibility, but modern decorator combinations are preferred.
Abstract methods may have implementations
An abstract method does not need to contain only pass. It can provide common behavior that subclasses call through super().
class Exporter(ABC):
@abstractmethod
def export(self, data):
self._validate(data)
def _validate(self, data):
if not data:
raise ValueError("empty data")
class JSONExporter(Exporter):
def export(self, data):
super().export(data)
return json.dumps(data)This creates a cooperative endpoint for multiple inheritance and avoids duplicated validation.
Partial implementations
A subclass may remain abstract.
class LoggingStorage(Storage):
def save(self, key, value):
print("saving", key)
return self._save_impl(key, value)
@abstractmethod
def _save_impl(self, key, value):
passIt implements part of the original contract and introduces a new obligation for concrete descendants.
Inspect abstract state
The metaclass maintains __abstractmethods__.
print(Storage.__abstractmethods__)
print(MemoryStorage.__abstractmethods__)Frameworks can use inspect.isabstract() for a clearer public API.
ABCMeta directly
from abc import ABCMeta
class Service(metaclass=ABCMeta):
@abstractmethod
def run(self):
passExplicit metaclass syntax helps when combining metaclasses, but inheriting from ABC is usually more readable.
Virtual subclasses
register() can treat an existing class as a subclass without modifying its inheritance.
class Reader(ABC):
@abstractmethod
def read(self):
pass
class LegacyReader:
def read(self):
return "data"
Reader.register(LegacyReader)
print(issubclass(LegacyReader, Reader))
print(isinstance(LegacyReader(), Reader))Registration does not add methods, call ABC code, or enforce the contract. It only changes issubclass() and isinstance() results.
Virtual-subclass risks
Registering an incompatible class creates a false sense of conformance.
class Incomplete:
pass
Reader.register(Incomplete)
print(isinstance(Incomplete(), Reader)) # TrueRegister only classes that already satisfy the contract semantically, and keep shared conformance tests.
register() as a decorator
@Reader.register
class OtherReader:
def read(self):
return "other"This keeps the intent near the implementation but also creates a direct dependency on the ABC.
__subclasshook__()
An ABC can recognize structural subclasses without explicit registration.
class CustomIterable(ABC):
@classmethod
def __subclasshook__(cls, C):
if cls is CustomIterable:
if any("__iter__" in B.__dict__ for B in C.__mro__):
return True
return NotImplementedClasses that define __iter__ in their hierarchy can now satisfy issubclass().
True, False, or NotImplemented
__subclasshook__() may return:
Trueto accept the class;Falseto reject it decisively;NotImplementedto continue normal checks.
Prefer NotImplemented when the hook cannot decide. Returning false is a strong override.
Keep hooks conservative
The presence of a name does not prove a compatible signature or meaning. A method named read can perform unrelated work.
Use hooks for well-known, easily recognized protocols. Rich contracts are safer with explicit inheritance, Protocol, and tests.
ABC versus Protocol
ABCs focus on nominal runtime relationships and shared implementation. typing.Protocol expresses structural typing for static checkers.
from typing import Protocol
class CanRead(Protocol):
def read(self) -> str: ...A class with a compatible method satisfies this protocol without inheritance. Runtime-checkable protocols offer limited runtime tests but do not deeply validate signatures.
ABC versus duck typing
Python can simply call obj.read() and handle AttributeError. This reduces coupling.
Use an ABC when central documentation, shared implementation, blocked construction of incomplete classes, or runtime registration provides concrete value.
Multiple inheritance
class Readable(ABC):
@abstractmethod
def read(self): ...
class Writable(ABC):
@abstractmethod
def write(self, data): ...
class VirtualFile(Readable, Writable):
def read(self):
return self.data
def write(self, data):
self.data = dataKeep cooperative methods using super() and avoid conflicting state in base classes.
Update abstract methods dynamically
Adding an implementation after class creation does not automatically recalculate abstract status. Use update_abstractmethods().
from abc import update_abstractmethods
class Dynamic(ABC):
@abstractmethod
def run(self):
pass
Dynamic.run = lambda self: "ok"
update_abstractmethods(Dynamic)
print(Dynamic().run())The official abc documentation recommends this helper for dynamic modification and assumes base classes were already updated.
ABC caches
ABCMeta caches subclass checks. get_cache_token() returns a token that changes when virtual-subclass registrations change.
from abc import get_cache_token
before = get_cache_token()
Reader.register(NewReader)
after = get_cache_token()
assert before != afterFrameworks with their own conformance caches can invalidate them when the token changes.
Plugin contracts
class Plugin(ABC):
name: str
@abstractmethod
def start(self, context):
pass
@abstractmethod
def stop(self):
passWhen loading a plugin, confirm that the class is concrete, validate metadata, and handle startup failures. Inheritance provides no security isolation.
Dependencies and tests
Receiving an ABC makes a dependency contract explicit.
class Application:
def __init__(self, storage: Storage):
self.storage = storageTests can provide a small fake implementation. Avoid unrestricted mocks that silently accept misspelled methods.
Evolving the API
Adding a new abstract method breaks every existing concrete subclass. It is a backward-incompatible change.
Consider a default implementation, a new ABC, or a separate capability interface before making new behavior mandatory.
Common mistakes
- Placing
@abstractmethodoutside@classmethodor@property. - Assuming a virtual subclass receives methods.
- Registering classes that do not satisfy the contract.
- Writing overly broad structural hooks.
- Adding abstract methods without compatibility planning.
- Building deep hierarchies to share little code.
- Treating an ABC as plugin isolation.
- Modifying a class without
update_abstractmethods().
Best practices
- Keep contracts small and cohesive.
- Provide common implementation where useful.
- Use
super()in cooperative inheritance. - Register virtual subclasses only after tests.
- Return
NotImplementedwhen hooks are uncertain. - Compare ABCs with Protocols and duck typing.
- Treat new abstract members as breaking changes.
- Run one shared conformance suite for implementations.
Conclusion
The Python abc module defines runtime contracts, prevents construction of incomplete classes, and shares behavior among implementations. ABC and abstractmethod cover most cases, while virtual registration and __subclasshook__() support structural integration.
Abstract classes work best when contracts are small, stable, and semantically clear. Conservative hooks, conformance tests, and careful API evolution keep extensions explicit without turning a project into a rigid hierarchy.





