The abc module provides tools for defining abstract base classes—interfaces that other classes must implement.

Basic Abstract Class

from abc import ABC, abstractmethod
 
class Animal(ABC):
    @abstractmethod
    def speak(self) -> str:
        """Return the sound this animal makes."""
        pass
    
    @abstractmethod
    def move(self) -> str:
        """Return how this animal moves."""
        pass
 
# Can't instantiate abstract class
# animal = Animal()  # TypeError
 
class Dog(Animal):
    def speak(self) -> str:
        return "Woof"
    
    def move(self) -> str:
        return "Run"
 
dog = Dog()  # Works

Abstract Properties

from abc import ABC, abstractmethod
 
class Shape(ABC):
    @property
    @abstractmethod
    def area(self) -> float:
        pass
    
    @property
    @abstractmethod
    def perimeter(self) -> float:
        pass
 
class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height
    
    @property
    def area(self) -> float:
        return self.width * self.height
    
    @property
    def perimeter(self) -> float:
        return 2 * (self.width + self.height)

Abstract Class Methods

from abc import ABC, abstractmethod
 
class Serializer(ABC):
    @classmethod
    @abstractmethod
    def from_json(cls, data: str) -> "Serializer":
        pass
    
    @abstractmethod
    def to_json(self) -> str:
        pass
 
class User(Serializer):
    def __init__(self, name: str):
        self.name = name
    
    @classmethod
    def from_json(cls, data: str) -> "User":
        import json
        return cls(**json.loads(data))
    
    def to_json(self) -> str:
        import json
        return json.dumps({"name": self.name})

Mixing Concrete and Abstract Methods

from abc import ABC, abstractmethod
 
class Database(ABC):
    @abstractmethod
    def connect(self) -> None:
        """Establish connection."""
        pass
    
    @abstractmethod
    def execute(self, query: str) -> list:
        """Execute a query."""
        pass
    
    # Concrete method using abstract methods
    def fetch_one(self, query: str):
        results = self.execute(query)
        return results[0] if results else None
    
    # Concrete method
    def close(self) -> None:
        print("Connection closed")

Virtual Subclasses with register()

Register a class as a "virtual subclass" without inheritance:

from abc import ABC, abstractmethod
 
class Iterable(ABC):
    @abstractmethod
    def __iter__(self):
        pass
 
# Register built-in str as Iterable
Iterable.register(str)
 
print(isinstance("hello", Iterable))  # True
print(issubclass(str, Iterable))      # True

Useful for integrating third-party classes.

subclasshook for Structural Typing

from abc import ABC, abstractmethod
 
class Hashable(ABC):
    @abstractmethod
    def __hash__(self):
        pass
    
    @classmethod
    def __subclasshook__(cls, C):
        if cls is Hashable:
            if hasattr(C, "__hash__"):
                return True
        return NotImplemented
 
# Any class with __hash__ is considered Hashable
class MyClass:
    def __hash__(self):
        return 42
 
print(isinstance(MyClass(), Hashable))  # True

When to Use ABCs vs Protocols

Use ABC when:

  • You want to enforce inheritance
  • Subclasses share implementation
  • You need register() for virtual subclasses

Use Protocol (typing) when:

  • You want structural typing (duck typing with type hints)
  • No inheritance required
  • Just checking interface compatibility
from abc import ABC, abstractmethod
from typing import Protocol
 
# ABC: requires inheritance
class Drawable(ABC):
    @abstractmethod
    def draw(self): pass
 
class Circle(Drawable):  # Must inherit
    def draw(self): pass
 
# Protocol: structural typing
class Renderable(Protocol):
    def render(self) -> str: ...
 
class Widget:  # No inheritance needed
    def render(self) -> str:
        return "<widget>"
 
def display(r: Renderable):  # Widget works here
    print(r.render())

Common ABCs from collections.abc

from collections.abc import (
    Iterable,      # __iter__
    Iterator,      # __iter__, __next__
    Sequence,      # __getitem__, __len__
    MutableSequence,  # + __setitem__, __delitem__, insert
    Mapping,       # __getitem__, __iter__, __len__
    MutableMapping,   # + __setitem__, __delitem__
    Set,           # __contains__, __iter__, __len__
    Callable,      # __call__
)
 
# Check if object is iterable
from collections.abc import Iterable
print(isinstance([1, 2, 3], Iterable))  # True
print(isinstance(42, Iterable))          # False

Practical Example: Plugin System

from abc import ABC, abstractmethod
 
class Plugin(ABC):
    @property
    @abstractmethod
    def name(self) -> str:
        """Unique plugin identifier."""
        pass
    
    @abstractmethod
    def initialize(self) -> None:
        """Called when plugin is loaded."""
        pass
    
    @abstractmethod
    def execute(self, data: dict) -> dict:
        """Process data and return result."""
        pass
    
    def cleanup(self) -> None:
        """Optional cleanup. Override if needed."""
        pass
 
class LoggingPlugin(Plugin):
    @property
    def name(self) -> str:
        return "logging"
    
    def initialize(self) -> None:
        print("Logging plugin initialized")
    
    def execute(self, data: dict) -> dict:
        print(f"Processing: {data}")
        return data
 
class PluginManager:
    def __init__(self):
        self.plugins: list[Plugin] = []
    
    def register(self, plugin: Plugin) -> None:
        if not isinstance(plugin, Plugin):
            raise TypeError("Must be a Plugin subclass")
        plugin.initialize()
        self.plugins.append(plugin)

Checking Abstract Methods

from abc import ABC, abstractmethod
import inspect
 
class Base(ABC):
    @abstractmethod
    def method1(self): pass
    
    @abstractmethod
    def method2(self): pass
 
# Get abstract methods
print(Base.__abstractmethods__)
# frozenset({'method1', 'method2'})
 
# Check if class is abstract
print(inspect.isabstract(Base))  # True

Quick Reference

from abc import ABC, abstractmethod
 
class MyABC(ABC):
    # Abstract method (must override)
    @abstractmethod
    def method(self): pass
    
    # Abstract property
    @property
    @abstractmethod
    def prop(self): pass
    
    # Abstract class method
    @classmethod
    @abstractmethod
    def class_method(cls): pass
    
    # Abstract static method
    @staticmethod
    @abstractmethod
    def static_method(): pass
    
    # Concrete method (inherited)
    def concrete(self):
        return "shared implementation"
 
# Register virtual subclass
MyABC.register(ExistingClass)
 
# Check abstract methods
MyABC.__abstractmethods__
FeatureUse Case
ABCBase class for ABCs
@abstractmethodMethod must be overridden
register()Virtual subclass without inheritance
__subclasshook__Custom isinstance/issubclass logic

ABCs define contracts. Use them when you need guaranteed interfaces, not just duck typing.

React to this post: