Fundamentals 13 min read

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.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Understanding Python Abstract Base Classes (ABCs) with Practical Examples

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.27431

This 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: 24

Here, 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: 20

Using 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.

design patternsOOPAbstract Base ClassesABCcollections-abc
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.