Abstract Base Classes with abc
Abstract base classes (ABCs) provide a way to define interfaces in Python. They let you specify which methods a subclass must implement without providing a full implementation yourself. The abc module, part of Python standard library, enables this pattern.
Why Use Abstract Base Classes?
When you create a class hierarchy, you sometimes want to define a base class that establishes a contract: any subclass must provide certain methods. Regular Python classes do not enforce this. Subclasses can omit methods, and your code will only fail at runtime when those methods are called.
Abstract base classes solve this problem by preventing instantiation of any subclass that has not implemented all required methods. The check happens at subclass creation time, not when you try to use the class.
Defining an Abstract Base Class
Use the ABC class and @abstractmethod decorator from the abc module:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
pass
@abstractmethod
def perimeter(self) -> float:
pass
The Shape class cannot be instantiated directly:
try:
s = Shape()
except TypeError as e:
print(e)
# Can't instantiate abstract class Shape with abstract method area
Subclasses must implement both methods or they also become abstract and cannot be instantiated:
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
return 3.14159 * self.radius ** 2
def perimeter(self) -> float:
return 2 * 3.14159 * self.radius
# This works
circle = Circle(5)
print(circle.area())
# 78.53975
Combining Abstract and Concrete Methods
Your abstract class can include both abstract and concrete methods. Concrete methods can use self and call abstract methods, knowing they will be provided by subclasses:
class Animal(ABC):
@abstractmethod
def speak(self) -> str:
pass
def announce(self) -> str:
return f"The animal says: {self.speak()}"
class Dog(Animal):
def speak(self) -> str:
return "woof"
class Cat(Animal):
def speak(self) -> str:
return "meow"
dog = Dog()
print(dog.announce())
# The animal says: woof
Abstract Properties
You can also define abstract properties using the @abstractproperty decorator or by combining @property with @abstractmethod:
from abc import ABC, abstractmethod
class Container(ABC):
@property
@abstractmethod
def size(self) -> int:
pass
@abstractmethod
def is_empty(self) -> bool:
pass
class Box(Container):
def __init__(self):
self._items = []
@property
def size(self) -> int:
return len(self._items)
def is_empty(self) -> bool:
return len(self._items) == 0
def add(self, item):
self._items.append(item)
box = Box()
print(box.is_empty())
# True
box.add("something")
print(box.size)
# 1
Abstract Class Methods and Static Methods
Abstract classes can include class methods and static methods. The abstract method can be of any type:
from abc import ABC, abstractmethod
class Parser(ABC):
@abstractmethod
@classmethod
def from_string(cls, s: str) -> "Parser":
pass
class JSONParser(Parser):
@classmethod
def from_string(cls, s: str) -> "JSONParser":
return cls()
json_parser = JSONParser.from_string('{"key": "value"}')
print(type(json_parser).__name__)
# JSONParser
Registering Virtual Subclasses
You can register classes as virtual subclasses of an ABC without using inheritance. This is useful for adapting existing classes to your interface:
class Format(ABC):
@abstractmethod
def convert(self, data: str) -> str:
pass
# Register a class as a subclass
@Format.register
class UppercaseFormat:
def convert(self, data: str) -> str:
return data.upper()
# It now passes isinstance checks
upper = UppercaseFormat()
print(isinstance(upper, Format))
# True
print(upper.convert("hello"))
# HELLO
When to Use Abstract Base Classes
Abstract base classes work well when you need to:
- Define a common interface for related classes
- Enforce method implementation in subclasses
- Create polymorphic code that works with any subclass
- Document expected method signatures
For simple cases where you only need to check if an object has certain methods, consider duck typing with hasattr or the Protocol class from typing instead. Protocols provide structural subtyping without explicit inheritance.
See Also
- Python Dataclasses — simplified class definitions for data storage
- Python Decorators — function and class decorators for modifying behavior
- Error Handling — try/except patterns for handling exceptions