Abstract Classes in Python

When it comes to writing reusable and extensible code in Python, abstract classes are an essential tool in your arsenal. Abstract classes define a set of methods that a subclass must implement, but it doesn’t provide an implementation for those methods itself. This feature makes them a powerful way to enforce certain behavior while also allowing for customization and flexibility in your code. In this post, we’ll explore what abstract classes are, their use cases, and some patterns you can use with them.

What are Abstract Classes?

An abstract class is a special kind of class that cannot be instantiated directly. Instead, it is meant to be subclassed, and the subclass must provide implementations for its abstract methods. Abstract classes are a way to define a blueprint or a template for a set of related classes. They allow you to define a set of methods that must be implemented in each subclass, without actually implementing those methods in the abstract class itself.

In Python, you can define an abstract class by using the abc module. This module provides the ABC class, which you can subclass to create your own abstract classes. The ABC class doesn’t do anything on its own, but it marks the class as abstract and allows you to define abstract methods.

Here’s an example:

from abc import ABC, abstractmethod

class Vehicle(ABC):
    @abstractmethod
    def start(self):
        pass

    @abstractmethod
    def stop(self):
        pass

In this example, we define an abstract class Vehicle with two abstract methods: start and stop. These methods don’t have an implementation, but any subclass of Vehicle must implement them.

Use Cases for Abstract Classes

Creating a plugin system

If you’re building a system where you want other developers to be able to create plugins, you can define an abstract class that they must subclass to create a plugin. This ensures that all plugins conform to a common interface and have the same behavior.

For example, if you’re building a text editor and you want to allow developers to create new file formats, you can define an abstract class FileFormat with methods like load and save. Then, any developer who wants to create a new file format can subclass FileFormat and implement those methods.

from abc import ABC, abstractmethod

class FileFormat(ABC):
    @abstractmethod
    def load(self, file_path):
        pass
    
    @abstractmethod
    def save(self, file_path):
        pass

class JSONFile(FileFormat):
    def load(self, file_path):
        # load the file as JSON
        pass
    
    def save(self, file_path):
        # save the file as JSON
        pass

class CSVFile(FileFormat):
    def load(self, file_path):
        # load the file as CSV
        pass
    
    def save(self, file_path):
        # save the file as CSV
        pass

In this example, we define an abstract class FileFormat that has two abstract methods: load and save. We then create two subclasses, JSONFile and CSVFile, which implement those methods. Any other developer who wants to create a new file format can also subclass FileFormat and implement the load and save methods.

Defining an interface for a library

When building a library, you may want to define an interface that other developers must use to interact with your library. This interface can be defined as an abstract class, which other developers can subclass to create their own classes that interact with your library.

For example, if you’re building a database library, you can define an abstract class Database with methods like connect, execute_query, and close. Then, other developers can create their own database classes that subclass Database and provide implementations for those methods.

from abc import ABC, abstractmethod

class Database(ABC):
    @abstractmethod
    def connect(self):
        pass
    
    @abstractmethod
    def execute_query(self, query):
        pass
    
    @abstractmethod
    def close(self):
        pass

class MySQLDatabase(Database):
    def connect(self):
        # connect to a MySQL database
        pass
    
    def execute_query(self, query):
        # execute a query on the database
        pass
    
    def close(self):
        # close the database connection
        pass

class PostgreSQLDatabase(Database):
    def connect(self):
        # connect to a PostgreSQL database
        pass
    
    def execute_query(self, query):
        # execute a query on the database
        pass
    
    def close(self):
        # close the database connection
        pass

In this example, we define an abstract class Database that has three abstract methods: connect, execute_query, and close. We then create two subclasses, MySQLDatabase and PostgreSQLDatabase, which implement those methods. Other developers can create their own database classes that subclass Database and provide implementations for those methods.

Ensuring consistency in a hierarchy of classes

If you’re building a hierarchy of related classes, you can use an abstract class to ensure that all the classes in the hierarchy have the same interface. This can help you avoid bugs and make your code more maintainable.

For example, if you’re building a game with different types of enemies, you can define an abstract class Enemy with methods like attack and take_damage. Then, you can create different subclasses of Enemy for each type of enemy in your game.

from abc import ABC, abstractmethod

class Enemy(ABC):
    @abstractmethod
    def attack(self):
        pass
    
    @abstractmethod
    def take_damage(self, damage):
        pass

class Goblin(Enemy):
    def attack(self):
        # implement the Goblin's attack
        pass
    
    def take_damage(self, damage):
        # implement the Goblin's take_damage method
        pass

class Ogre(Enemy):
    def attack(self):
        # implement the Ogre's attack
        pass
    
    def take_damage(self, damage):
        # implement the Ogre's take_damage method
        pass

In this example, we define an abstract class Enemy that has two abstract methods: attack and take_damage. We then create two subclasses, Goblin and Ogre, which implement those methods. Any other subclasses that we create for different types of enemies in the game will also have to implement those methods, ensuring consistency in the hierarchy of classes.

Patterns for Abstract Classes

Template Method Pattern

The Template Method Pattern is a design pattern that uses an abstract class to define a template for an algorithm, with some steps that are defined by the abstract class and some steps that must be defined by a subclass.

One use case for the Template Method Pattern is when you have a consistent algorithm that needs to be implemented across multiple subclasses, with some variations. For example, imagine that you’re building a game with different types of enemies, and each type of enemy has a different way of attacking. You could define an abstract class Enemy with a template method called attack, which defines the overall steps for an attack, such as selecting a target, positioning, and attacking the target. The individual steps would be implemented as abstract methods that must be defined by each enemy subclass.

Here’s an example implementation of the Template Method Pattern for the Enemy use case in we talked about earlier (before and after applying the pattern):

class Enemy:
    def __init__(self, name, health, damage):
        self.name = name
        self.health = health
        self.damage = damage

    def attack(self, target):
        # Attack the target

    def move(self):
        # Move to a new location

class Goblin(Enemy):
    def take_turn(self, target):
        self.attack(target)
        self.move()

class Orc(Enemy):
    def take_turn(self, target):
        self.move()
        self.attack(target)

In this example, the Goblin attacks before moving, while the Orc moves before attacking. This can lead to inconsistent gameplay and make it difficult to balance the game. To ensure consistent algorithms across related classes, we should use the Template Method pattern.

Applying Template Method pattern:

from abc import ABC, abstractmethod

class Enemy(ABC):
    def __init__(self, name, health, damage):
        self.name = name
        self.health = health
        self.damage = damage

    def take_turn(self, target):
        self.select_target(target)
        self.attack_target()
        self.move_to_new_location()

    @abstractmethod
    def select_target(self, target):
        pass

    @abstractmethod
    def attack_target(self):
        pass

    @abstractmethod
    def move_to_new_location(self):
        pass

class Goblin(Enemy):
    def select_target(self, target):
        # Select the target

    def attack_target(self):
        # Attack the target

    def move_to_new_location(self):
        # Move to a new location

class Orc(Enemy):
    def select_target(self, target):
        # Select the target

    def attack_target(self):
        # Attack the target

    def move_to_new_location(self):
        # Move to a new location

In this example, the Enemy class defines a template method called take_turn, which defines the overall steps for an enemy’s turn in the game. Each step is defined as an abstract method that must be implemented by each enemy subclass. This ensures that each enemy takes its turn in a consistent way, making it easier to balance the game and create a consistent player experience.

The Strategy Pattern

The Strategy Pattern is a design pattern that uses an abstract class to define a family of algorithms that can be selected and used dynamically at runtime.

Suppose you are building a payment system that can accept payments from multiple payment gateways like PayPal, Stripe, and Square. Each payment gateway has its own API and different requirements for making and processing payments. You want to build a flexible payment system that can switch between different payment gateways at runtime, without modifying the code.

In this scenario, you can use the Strategy Pattern. You can define an abstract class PaymentStrategy with an abstract method called process_payment. Then you can create concrete classes that implement this method and provide the specific implementation for each payment gateway.

rom abc import ABC, abstractmethod

class PaymentStrategy(ABC):
    @abstractmethod
    def process_payment(self, amount):
        pass

class PayPalStrategy(PaymentStrategy):
    def process_payment(self, amount):
        # Code to process payment via PayPal API
        print(f"Processing payment of ${amount} via PayPal...")

class StripeStrategy(PaymentStrategy):
    def process_payment(self, amount):
        # Code to process payment via Stripe API
        print(f"Processing payment of ${amount} via Stripe...")

class SquareStrategy(PaymentStrategy):
    def process_payment(self, amount):
        # Code to process payment via Square API
        print(f"Processing payment of ${amount} via Square...")

class PaymentContext:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def set_strategy(self, strategy: PaymentStrategy):
        self.strategy = strategy

    def process_payment(self, amount):
        self.strategy.process_payment(amount)

# Example usage
paypal = PayPalStrategy()
stripe = StripeStrategy()
square = SquareStrategy()

payment = PaymentContext(paypal)
payment.process_payment(100)

payment.set_strategy(stripe)
payment.process_payment(200)

payment.set_strategy(square)
payment.process_payment(300)

In this example, PaymentStrategy is the abstract class that defines the strategy interface. PayPalStrategy, StripeStrategy, and SquareStrategy are the concrete classes that implement this interface with their specific payment processing logic.

The PaymentContext class is the context class that holds the current strategy and provides a public method process_payment to initiate the payment processing. The set_strategy method allows you to switch between different payment gateways at runtime.

You can see that the process_payment method of each concrete class contains specific code to process the payment via the corresponding API. The context class does not need to know about these specific implementation details. It just delegates the payment processing to the current strategy object, which can be changed dynamically.


In this post, we’ve explored the concept of abstract classes in Python. We learned that abstract classes are classes that cannot be instantiated and that are intended to be subclassed by concrete classes. We also discussed some use cases for abstract classes, such as defining interfaces and implementing common behaviors across related classes.

We then delved into two common design patterns that use abstract classes: the template method pattern and the strategy pattern. The template method pattern allows you to define a skeleton of an algorithm in an abstract class, with specific steps implemented in concrete subclasses. The strategy pattern allows you to define a family of algorithms, encapsulate each one as a separate class, and make them interchangeable at runtime.

By using abstract classes and design patterns effectively, you can write more modular, maintainable, and extensible code.

If you’re interested in learning more about abstract classes in Python, you can check out the official Python documentation on abstract base classes: https://docs.python.org/3/library/abc.html. You may also want to explore other design patterns that use abstract classes, such as the factory method pattern and the iterator pattern.