Understanding Python Abstract Base Classes (ABCs) with Practical Examples
This article explains the purpose and usage of Python's Abstract Base Classes, demonstrates how to define and register ABCs, and provides multiple code examples—including geometric shapes, collections, database connections, file handlers, and command patterns—to illustrate their practical application.
Preface
Abstract Base Classes (ABCs) are a module in Python's standard library located in collections.abc . They provide a way to define interfaces, enforcing subclasses to follow certain contracts, thereby enabling polymorphism and interface consistency.
Purpose of Abstract Base Classes
The main purpose of an ABC is to specify an interface without providing concrete implementations. By defining required methods, ABCs ensure that all subclasses implement those methods, guaranteeing consistent behavior.
How to Use Abstract Base Classes
Using ABCs typically involves the following steps:
Import the abc module.
Define an abstract base class using abc.ABCMeta as the metaclass and mark abstract methods with the @abstractmethod decorator.
Implement subclasses that provide concrete implementations for all abstract methods.
Code Example: Defining an Abstract Base Class
import abc
class Polygon(metaclass=abc.ABCMeta):
@abc.abstractmethod
def area(self):
"""Calculate the area of the polygon"""
pass
class Square(Polygon):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
class Circle(Polygon):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
square = Square(5)
circle = Circle(3)
print(square.area()) # Output: 25
print(circle.area()) # Output: 28.27431This example defines an abstract base class Polygon with an abstract method area , and concrete subclasses Square and Circle that implement the method.
Using the Registration Mechanism
Beyond inheritance, you can register a class as a virtual subclass of an ABC using the register method, allowing existing classes to be recognized as subclasses without direct inheritance.
import abc
class Polygon(metaclass=abc.ABCMeta):
@abc.abstractmethod
def area(self):
"""Calculate the area of the polygon"""
pass
@Polygon.register
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
rectangle = Rectangle(4, 6)
print(rectangle.area()) # Output: 24Here, Rectangle is registered as a subclass of Polygon even though it does not inherit from it directly.
Using collections.abc Module
The collections.abc module provides predefined ABCs such as Sequence , Set , and Mapping for more specific interface definitions.
from collections.abc import Sequence
class MyList(Sequence):
def __init__(self, iterable):
self._items = list(iterable)
def __len__(self):
return len(self._items)
def __getitem__(self, index):
return self._items[index]
my_list = MyList([1, 2, 3, 4, 5])
print(len(my_list)) # Output: 5
print(my_list[2]) # Output: 3
print(list(my_list)) # Output: [1, 2, 3, 4, 5]Advanced Example: Geometric Shapes
import abc
class Shape(metaclass=abc.ABCMeta):
@abc.abstractmethod
def area(self):
pass
@abc.abstractmethod
def perimeter(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
circle = Circle(5)
rectangle = Rectangle(4, 6)
print(circle.area()) # Output: 78.53975
print(circle.perimeter()) # Output: 31.4159
print(rectangle.area()) # Output: 24
print(rectangle.perimeter())# Output: 20Using collections.abc to Define Custom Containers
from collections.abc import Iterable, Iterator
class MyContainer(Iterable):
def __init__(self, items):
self.items = items
def __iter__(self):
return iter(self.items)
class MyList(MyContainer):
def __init__(self, *items):
super().__init__(items)
def append(self, item):
self.items.append(item)
container = MyContainer([1, 2, 3])
my_list = MyList(4, 5, 6)
for item in container:
print(item) # Output: 1 2 3
my_list.append(7)
print(list(my_list)) # Output: [4, 5, 6, 7]Abstract Base Class for Database Connections
import abc
class DatabaseConnection(metaclass=abc.ABCMeta):
@abc.abstractmethod
def connect(self):
pass
@abc.abstractmethod
def execute(self, query):
pass
@abc.abstractmethod
def close(self):
pass
class MySQLConnection(DatabaseConnection):
def connect(self):
print("Connecting to MySQL database...")
def execute(self, query):
print(f"Executing query: {query}")
def close(self):
print("Closing MySQL connection...")
class SQLiteConnection(DatabaseConnection):
def connect(self):
print("Connecting to SQLite database...")
def execute(self, query):
print(f"Executing query: {query}")
def close(self):
print("Closing SQLite connection...")
mysql_conn = MySQLConnection()
sqlite_conn = SQLiteConnection()
mysql_conn.connect()
mysql_conn.execute("SELECT * FROM users")
mysql_conn.close()
sqlite_conn.connect()
sqlite_conn.execute("SELECT * FROM products")
sqlite_conn.close()Abstract Base Class for File Handling
import abc
class FileHandler(metaclass=abc.ABCMeta):
@abc.abstractmethod
def read(self):
pass
@abc.abstractmethod
def write(self, data):
pass
class TextFileHandler(FileHandler):
def __init__(self, filename):
self.filename = filename
def read(self):
with open(self.filename, 'r') as file:
return file.read()
def write(self, data):
with open(self.filename, 'w') as file:
file.write(data)
class BinaryFileHandler(FileHandler):
def __init__(self, filename):
self.filename = filename
def read(self):
with open(self.filename, 'rb') as file:
return file.read()
def write(self, data):
with open(self.filename, 'wb') as file:
file.write(data)
text_handler = TextFileHandler("example.txt")
binary_handler = BinaryFileHandler("example.bin")
text_handler.write("Hello, world!")
print(text_handler.read()) # Output: Hello, world!
binary_handler.write(b"Hello, world!")
print(binary_handler.read()) # Output: b'Hello, world!'Abstract Base Class for Command Pattern
import abc
class Command(metaclass=abc.ABCMeta):
@abc.abstractmethod
def execute(self):
pass
class Light:
def turn_on(self):
print("Turning on the light...")
def turn_off(self):
print("Turning off the light...")
class TurnOnLightCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
class TurnOffLightCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_off()
light = Light()
turn_on_command = TurnOnLightCommand(light)
turn_off_command = TurnOffLightCommand(light)
turn_on_command.execute() # Output: Turning on the light...
turn_off_command.execute() # Output: Turning off the light...Conclusion
Through these examples, we see how ABCs can define clear interfaces and enforce consistent implementation across subclasses, improving code design quality and maintainability. Python's abc module, together with the registration mechanism and the predefined ABCs in collections.abc , provides a powerful toolkit for building robust, well‑structured applications.
Test Development Learning Exchange
Test Development Learning Exchange
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.