Python abc: Abstract Base Classes

Published on: August 6, 2026
Reading time: 5 minutes
Software architecture representing abstract base classes with Python abc

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):
        pass

Calling 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):
        pass

The order matters because abstractmethod marks the underlying descriptor.

Abstract properties

class Document(ABC):
    @property
    @abstractmethod
    def title(self):
        pass

A subclass implements another property.

class Report(Document):
    def __init__(self, title):
        self._title = title

    @property
    def title(self):
        return self._title

Older 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):
        pass

It 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):
        pass

Explicit 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))  # True

Register 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 NotImplemented

Classes that define __iter__ in their hierarchy can now satisfy issubclass().

True, False, or NotImplemented

__subclasshook__() may return:

  • True to accept the class;
  • False to reject it decisively;
  • NotImplemented to 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 = data

Keep 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 != after

Frameworks 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):
        pass

When 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 = storage

Tests 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 @abstractmethod outside @classmethod or @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 NotImplemented when 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.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    A vibrant array of colored thread spools neatly organized in rows, perfect for sewing enthusiasts.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python queue: Coordinate Threads

    Learn Python queue to coordinate threads with FIFO, priority, backpressure, task tracking, retries, and graceful shutdown.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Extreme close-up of computer code displaying various programming terms and elements.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python struct: Pack Binary Data

    Learn Python struct to pack binary values, define endianness, reuse buffers, parse records, and validate external protocols safely.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python tarfile: Create Safe TARs

    Learn Python tarfile to create compressed TARs, inspect members, and extract archives with filters, limits, and path traversal protection.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Row of colorful office binders neatly arranged on a shelf, ideal for organization concepts.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python gzip: Compress .gz Files

    Learn Python gzip to read and write .gz files, produce reproducible streams, process large data, and limit external expansion.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Neatly arranged blue office binders labeled with dates and names for organized storage.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python lzma: Compress XZ Files

    Learn Python lzma to create XZ files, process streams, select checks and filters, and enforce memory limits on external data.

    Ler mais

    Tempo de leitura: 4 minutos
    17/08/2026
    Detailed close-up texture of a snake's patterned skin showcasing natural patterns and scales.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python bz2: Compress with bzip2

    Learn Python bz2 to compress files and bytes, process data incrementally, handle concatenated streams, and limit external expansion.

    Ler mais

    Tempo de leitura: 4 minutos
    16/08/2026