Alex Lin Wang 王帅

ABC (Abstract Base Classes)

January 3, 2025 • 4 min read
Prevent runtime errors by enforcing method implementation at instantiation time with Python's Abstract Base Classes. Learn how to build safer class hierarchies that catch missing methods before they reach production.

Imagine you are designing a set of class hierarchies where you have a parent class with an "abstract" method that needs to be implemented by subclasses to work.

Generally, when you do this, you will use a NotImplementedError in the methods. However there are two major problems:

  • Declaring the class succeeds even when certain subclass required methods are not implemented.
  • Calling that not-yet implemented method of your subclass fails because it wasn't caught at instantiation-time.

The most common example is something like this:

import abc

class StorageService:
    @abc.abstractmethod
    def save(self, key, value):
        raise NotImplementedError("Subclasses must implement 'save'")
    
    @abc.abstractmethod
    def load(self, key):
        raise NotImplementedError("Subclasses must implement 'load'")

class InMemoryStorage(StorageService):
    def save(self, key, value):
        print(f"[InMemory] Saving {key} -> {value}")
        # stores it somewhere in memory (fake impl for demo)
        self._data = {key: value}

# Instantiation succeeds
store = InMemoryStorage()
store.save("user:1", {\"name\": \"Alex\"})

# But calling the missing method fails only at runtime
print(store.load("user:1"))  # NotImplementedError

In the above example, you can still create InMemoryStorage even though load() isn't defined. Only when another engineer (or your API call) tries load() do you hit a NotImplementedError — potentially in production—do you realize this problem.

The Solution: ABC with ABCMeta

To resolve this, use the built-in abc.ABCMeta metaclass from abc to define abstract classes. This basically prevents you from instantiating a class at creation time with a TypeError, rather than failing later.

import abc
from abc import ABC, abstractmethod

class StorageService(ABC):  # or metaclass=abc.ABCMeta
    @abstractmethod
    def save(self, key, value):
        """Save a value by key"""
        pass
    
    @abstractmethod
    def load(self, key):
        """Load a value by key"""
        pass

class InMemoryStorage(StorageService):
    def save(self, key, value):
        print(f"[InMemory] Saving {key} -> {value}")
        self._data = {key: value}
    # Oops: still forgot load()!

# Instantiation FAILS immediately
store = InMemoryStorage()
# TypeError: Can't instantiate abstract class InMemoryStorage with abstract method load

Now your instantiations will be safe! The TypeError happens at the moment you try to create an instance, not later when you call the missing method. This catches errors during development rather than in production.

Key Benefits

  • Early error detection: Missing implementations are caught at instantiation time, not runtime
  • Clear contracts: Abstract methods explicitly define what subclasses must implement
  • Better IDE support: Most IDEs will warn about missing abstract method implementations
  • Type checker friendly: Tools like mypy understand and enforce abstract base classes

Questions or feedback? Feel free to reach out!