Skip to content

Abstract Base Classes#

Abstract Base Classes (ABCs) define interfaces — contracts that subclasses must fulfill — without providing complete implementations. Python's abc module lets you declare "this method must exist" and fail fast at instantiation time if it doesn't, rather than at call time with NotImplementedError.

This page extends the ABC preview in Classes and OOP Basics and Introdicion to OOPs.

How to use this page

Read after inheritance basics. Related: Polymorphism, Inheritance and Multiple Inheritance, @property Decorators.

At a glance
Track Python Advanced → Object Oriented Programming
Sections 12 major topics
Outline Use the right-hand TOC to jump

Topics: Why ABCs exist in Python · Minimal ABC example · @abstractmethod rules (Python 3.10+) · Abstract properties, classmethods, staticmethods · ABC vs NotImplementedError — two patterns · Virtual subclasses — register() · __subclasshook__ — structural isinstance · Collections ABCs — practical everyday use · … (+4 more)

  1. Why ABCs exist in Python
  2. Minimal ABC example
  3. @abstractmethod rules (Python 3.10+)
  4. Abstract properties, classmethods, staticmethods
  5. ABC vs NotImplementedError — two patterns
  6. Virtual subclasses — register()
  7. __subclasshook__ — structural isinstance
  8. Collections ABCs — practical everyday use
  9. ABC + multiple inheritance
  10. typing.Protocol vs abc.ABC
  11. Interview pattern — plugin architecture
  12. Interview pattern — strategy via ABC

Why ABCs exist in Python#

Python has no interface keyword. Duck typing works for most code, but sometimes you need:

  • Enforced contracts — plugins, frameworks, team APIs
  • Fail-fast errorsTypeError at construction, not deep in a call stack
  • Documentation — explicit list of required methods
  • Registration — virtual subclasses without inheritance
Approach Enforcement Flexibility
Duck typing None — runtime attribute check Maximum
raise NotImplementedError Only when method called Medium
abc.ABC + @abstractmethod At instantiation Structured
typing.Protocol (3.8+) Static (type checker only) Structural typing

Minimal ABC example#

from abc import ABC, abstractmethod

class Storage(ABC):
    @abstractmethod
    def get(self, key: str) -> str:
        """Retrieve value by key."""

    @abstractmethod
    def put(self, key: str, value: str) -> None:
        """Store value by key."""

class MemoryStorage(Storage):
    def __init__(self):
        self._data: dict[str, str] = {}

    def get(self, key: str) -> str:
        return self._data[key]

    def put(self, key: str, value: str) -> None:
        self._data[key] = value

# Storage()              # TypeError: Can't instantiate abstract class
ms = MemoryStorage()     # OK
ms.put("x", "hello")
ms.get("x")              # "hello"

Missing an abstract method:

class BrokenStorage(Storage):
    def get(self, key: str) -> str:
        return ""

# BrokenStorage()   # TypeError: Can't instantiate abstract class BrokenStorage
#                   # with abstract method put

@abstractmethod rules (Python 3.10+)#

Rule Detail
Must override Every @abstractmethod in concrete subclass must be implemented
Can have body Abstract method may include default implementation — subclass still must override
Combines with others @abstractmethod must be innermost when stacked with @classmethod, @staticmethod, @property
Instantiation check Python walks the class __dict__ and MRO; any remaining abstract methods block instantiation
Metaclass ABC sets ABCMeta — you rarely touch this directly
class Base(ABC):
    @abstractmethod
    def process(self, data: str) -> str:
        return data.upper()   # default body — still abstract

class Child(Base):
    def process(self, data: str) -> str:
        return super().process(data) + "!"

Abstract properties, classmethods, staticmethods#

from abc import ABC, abstractmethod

class Shape(ABC):
    @property
    @abstractmethod
    def area(self) -> float: ...

    @classmethod
    @abstractmethod
    def unit(cls) -> "Shape": ...

    @staticmethod
    @abstractmethod
    def dimensions() -> int: ...

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    @property
    def area(self) -> float:
        return 3.14159 * self.radius ** 2

    @classmethod
    def unit(cls) -> "Circle":
        return cls(1.0)

    @staticmethod
    def dimensions() -> int:
        return 2

Decorator order matters: @abstractmethod is applied last (appears innermost / closest to def):

@property
@abstractmethod
def x(self): ...

Not:

@abstractmethod
@property   # wrong order in modern Python
def x(self): ...

ABC vs NotImplementedError — two patterns#

Template method with NotImplementedError#

class Parser:
    def parse(self, text: str) -> list:
        tokens = self.tokenize(text)
        return self.build(tokens)

    def tokenize(self, text: str) -> list[str]:
        raise NotImplementedError

    def build(self, tokens: list[str]) -> list:
        raise NotImplementedError

Works, but Parser() succeeds — failure happens on first parse() call.

ABC equivalent#

from abc import ABC, abstractmethod

class Parser(ABC):
    def parse(self, text: str) -> list:
        tokens = self.tokenize(text)
        return self.build(tokens)

    @abstractmethod
    def tokenize(self, text: str) -> list[str]: ...

    @abstractmethod
    def build(self, tokens: list[str]) -> list: ...

Parser() fails immediately. Prefer ABCs when the class should never be instantiated directly.


Virtual subclasses — register()#

A class can be considered a subclass without inheriting:

from abc import ABC, abstractmethod

class Closeable(ABC):
    @abstractmethod
    def close(self) -> None: ...

class FileWrapper:
    def close(self) -> None:
        print("closing")

Closeable.register(FileWrapper)

issubclass(FileWrapper, Closeable)   # True
isinstance(FileWrapper(), Closeable) # True — if all abstract methods exist

Registration is for isinstance/issubclass checks — it does not inject missing methods. FileWrapper must already implement close.


__subclasshook__ — structural isinstance#

For duck-typing-style ABC checks:

from abc import ABC, abstractmethod

class Iterable(ABC):
    @abstractmethod
    def __iter__(self): ...

    @classmethod
    def __subclasshook__(cls, subclass):
        if cls is Iterable:
            if any("__iter__" in b.__dict__ for b in subclass.__mro__):
                return True
        return NotImplemented

class MyRange:
    def __iter__(self):
        return iter(range(3))

issubclass(MyRange, Iterable)   # True — via __subclasshook__

Built-in ABCs like collections.abc.Sequence use similar hooks.


Collections ABCs — practical everyday use#

The collections.abc module provides ABCs for standard protocols:

from collections.abc import Sequence, Mapping, Iterator

def head(items: Sequence[int]) -> int:
    return items[0]

def lookup(table: Mapping[str, int], key: str) -> int:
    return table[key]

def consume(it: Iterator[int]) -> list[int]:
    return list(it)
ABC Requires
Iterable __iter__
Iterator __iter__ + __next__
Sequence __getitem__, __len__, index, count
Mapping __getitem__, __len__, __iter__, keys, items, values, get, ...
MutableMapping Mapping + __setitem__, __delitem__
Callable __call__

These integrate with isinstance checks and document expected behavior. See Dunder or Magic Methods.


ABC + multiple inheritance#

ABCs participate normally in MRO:

from abc import ABC, abstractmethod

class Readable(ABC):
    @abstractmethod
    def read(self) -> str: ...

class Writable(ABC):
    @abstractmethod
    def write(self, data: str) -> None: ...

class ReadWrite(Readable, Writable):
    def read(self) -> str:
        return "data"

    def write(self, data: str) -> None:
        print(data)

rw = ReadWrite()   # OK

See Method Resolution Order for diamond inheritance with ABCs.


typing.Protocol vs abc.ABC#

Python 3.8+ structural subtyping for static analysis:

from typing import Protocol

class SupportsClose(Protocol):
    def close(self) -> None: ...

def cleanup(resource: SupportsClose) -> None:
    resource.close()
Feature abc.ABC typing.Protocol
Runtime enforcement Yes (instantiation) No (type checker only)
Inheritance required Yes (or register) No — structural match
isinstance at runtime Yes With @runtime_checkable only
Best for Frameworks, plugins Type hints, API documentation
from typing import Protocol, runtime_checkable

@runtime_checkable
class SupportsLen(Protocol):
    def __len__(self) -> int: ...

isinstance([1, 2], SupportsLen)   # True

Use both when you want runtime checks and static typing — ABC for instantiation, Protocol for function signatures.


Interview pattern — plugin architecture#

from abc import ABC, abstractmethod

class PaymentProcessor(ABC):
    @abstractmethod
    def charge(self, amount: float) -> bool: ...

    @abstractmethod
    def refund(self, transaction_id: str) -> bool: ...

class StripeProcessor(PaymentProcessor):
    def charge(self, amount: float) -> bool:
        print(f"Stripe: charged ${amount}")
        return True

    def refund(self, transaction_id: str) -> bool:
        print(f"Stripe: refunded {transaction_id}")
        return True

def checkout(processor: PaymentProcessor, amount: float) -> None:
    if processor.charge(amount):
        print("Payment successful")

checkout(StripeProcessor(), 49.99)

This demonstrates Polymorphism with an explicit contract.


Interview pattern — strategy via ABC#

from abc import ABC, abstractmethod

class SortStrategy(ABC):
    @abstractmethod
    def sort(self, data: list[int]) -> list[int]: ...

class QuickSort(SortStrategy):
    def sort(self, data: list[int]) -> list[int]:
        return sorted(data)   # simplified

class MergeSort(SortStrategy):
    def sort(self, data: list[int]) -> list[int]:
        return sorted(data)   # simplified

class Sorter:
    def __init__(self, strategy: SortStrategy):
        self._strategy = strategy

    def run(self, data: list[int]) -> list[int]:
        return self._strategy.sort(data)

Composition + ABC beats deep inheritance hierarchies for swappable algorithms.


Interview traps (quick reference)#

Trap What goes wrong Safe approach
Forgetting @abstractmethod Base class instantiable with empty body Decorate every interface method
Wrong decorator order TypeError or method not abstract @property / @classmethod above @abstractmethod
Assuming register() adds methods isinstance True but methods missing Implement methods first, then register
ABC for every class Over-engineering Duck typing unless contract needed
Confusing Protocol with ABC No runtime enforcement from Protocol alone Pick based on runtime vs static needs
Partial implementation in MRO Still abstract if any parent method missing Implement all abstract methods in concrete class

Mental model checklist#

  1. When does Python raise TypeError for an ABC vs NotImplementedError at call time?
  2. How do abstract properties differ from abstract methods syntactically?
  3. What does Closeable.register(FileWrapper) actually do?
  4. When would you choose typing.Protocol over abc.ABC?
  5. How do collections.abc types relate to dunder methods?

What's next#

Topic Page
Polymorphism in practice Polymorphism
Inheritance design Inheritance and Multiple Inheritance
MRO with ABCs Method Resolution Order
Intermediate OOP Classes and OOP Basics