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() # WorksAbstract 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)) # TrueUseful 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)) # TrueWhen 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)) # FalsePractical 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)) # TrueQuick 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__| Feature | Use Case |
|---|---|
ABC | Base class for ABCs |
@abstractmethod | Method 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: