How to Use Abstract Base Classes (ABC) in Python

I have often seen projects turn into a “Wild West” of inconsistent method names and missing logic.

I remember working on a large-scale financial engine where different developers created their own versions of “payment” classes, causing the entire system to crash during a production run.

That was the day I truly realized the power of Abstract Base Classes (ABC) in Python. They act as a blueprint, ensuring that every subclass follows a strict set of rules before the code even runs.

In this tutorial, I will show you how to use the abc module to create robust, maintainable Python code using real-world scenarios.

What is an Abstract Base Class in Python?

An Abstract Base Class (ABC) is a class that cannot be instantiated on its own. Its primary purpose is to define a common interface for a group of subclasses.

Think of it like a federal regulation in the US banking system. The regulation defines what a bank must do (like processing a withdrawal), but it doesn’t tell them exactly how to build their internal software to do it.

Why Should You Use ABCs?

When I lead development teams, I use ABCs to enforce “contracts.” If a junior developer creates a new class based on my ABC, Python will raise an error if they forget to implement a required method.

It saves hours of debugging time by catching architectural errors early in the development cycle.

Method 1: Use the abc Module with @abstractmethod

This is the most common way to implement ABCs in Python. We use the built-in abc module to define our base structure.

In this example, let’s imagine we are building a payroll system for a US-based corporation that handles different types of employees like Full-Time and Hourly contractors.

First, we need to import the necessary decorators and define our base class.

from abc import ABC, abstractmethod

# Defining the Abstract Base Class
class Employee(ABC):
    
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id

    @abstractmethod
    def calculate_payroll(self):
        """All subclasses must implement this method to calculate pay."""
        pass

    @abstractmethod
    def get_tax_filing_type(self):
        """Returns the US Tax form type (W2 or 1099)."""
        pass

# Implementing a Full-Time Employee subclass
class FullTimeEmployee(Employee):
    def __init__(self, name, employee_id, annual_salary):
        super().__init__(name, employee_id)
        self.annual_salary = annual_salary

    def calculate_payroll(self):
        return self.annual_salary / 24  # Assuming semi-monthly pay periods

    def get_tax_filing_type(self):
        return "Form W-2"

# Implementing an Hourly Contractor subclass
class HourlyContractor(Employee):
    def __init__(self, name, employee_id, hourly_rate, hours_worked):
        super().__init__(name, employee_id)
        self.hourly_rate = hourly_rate
        self.hours_worked = hours_worked

    def calculate_payroll(self):
        return self.hourly_rate * self.hours_worked

    def get_tax_filing_type(self):
        return "Form 1099-NEC"

# Testing the implementation
employees = [
    FullTimeEmployee("Alice Smith", "FT-101", 120000),
    HourlyContractor("Bob Jones", "C-502", 75, 80)
]

for emp in employees:
    print(f"Employee: {emp.name} | Pay: ${emp.calculate_payroll():,.2f} | Tax: {emp.get_tax_filing_type()}")

You can see the output in the screenshot below.

Abstract Base Classes (ABC) in Python

I prefer this method because it is explicit. If I try to create an instance of Employee directly, Python will throw a TypeError.

This prevents the system from accidentally processing a “generic” employee that doesn’t have a defined salary or tax status.

Method 2: Define Abstract Properties

Sometimes, you don’t just want to enforce methods; you want to ensure that every subclass has a specific attribute or property.

In the US logistics industry, every vehicle in a fleet must have a Department of Transportation (DOT) ID. We can enforce this using @property combined with @abstractmethod.

from abc import ABC, abstractmethod

class FleetVehicle(ABC):

    @property
    @abstractmethod
    def dot_id(self):
        """Every vehicle must have a registered DOT ID."""
        pass

    @abstractmethod
    def calculate_maintenance_schedule(self):
        pass

class SemiTruck(FleetVehicle):
    def __init__(self, truck_id):
        self._truck_id = truck_id

    @property
    def dot_id(self):
        return f"DOT-USA-{self._truck_id}"

    def calculate_maintenance_schedule(self):
        return "Every 15,000 miles"

# This will work
my_truck = SemiTruck("5589")
print(f"Vehicle {my_truck.dot_id} Schedule: {my_truck.calculate_maintenance_schedule()}")

You can see the output in the screenshot below.

Use Abstract Base Classes (ABC) in Python

In my experience, enforcing properties is crucial when you are integrating with third-party APIs or databases that require specific ID formats.

It ensures that no one “forgets” to include the identifier, which would otherwise cause the database to reject the record.

Method 3: Virtual Subclasses using register()

This is a more advanced technique that I’ve used when working with legacy codebases where I couldn’t modify the original class hierarchy.

You can “register” a class as a subclass of an ABC even if it doesn’t inherit from it directly.

from abc import ABC

class USRegulatoryBody(ABC):
    @classmethod
    def __subclasshook__(cls, C):
        if cls is USRegulatoryBody:
            if any("audit" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

class LocalBank:
    def audit(self):
        return "Internal Audit Completed"

# Registering manually
USRegulatoryBody.register(LocalBank)

# Checking relationship
print(f"Is LocalBank a subclass? {issubclass(LocalBank, USRegulatoryBody)}")
print(f"Is instance of LocalBank an ABC instance? {isinstance(LocalBank(), USRegulatoryBody)}")

You can see the output in the screenshot below.

How to Use Abstract Base Classes (ABC) in Python

I use this sparingly. It’s a “ninja” move for when you want the benefits of type checking without refactoring thousands of lines of existing code.

It allows your new logic to recognize old classes as “valid” parts of your new architecture.

Best Practices for Using ABCs

After years of code reviews, here are my “golden rules” for Abstract Base Classes:

  1. Don’t Over-Engineer: If you only have two classes that are very different, you probably don’t need an ABC.
  2. Keep Interfaces Small: I find it’s better to have several small ABCs (e.g., Taxable, Insured) than one giant BusinessEntity class.
  3. Use Meaningful Names: Always name your abstract methods based on what they should do, not how they do it.

Handle Errors in ABCs

One of the most common mistakes I see is developers forgetting that you can’t instantiate an ABC.

If you see this error: TypeError: Can’t instantiate abstract class Employee with abstract method calculate_payroll

It means you (or your teammate) forgot to write the code for calculate_payroll in the child class. This is actually a good thing, the ABC is doing its job!

Advanced Real-World Example: US Real Estate System

Let’s look at a complex example. Suppose we are building a system to calculate property taxes across different US states. Each state has its own formula, but the system needs a unified way to call them.

from abc import ABC, abstractmethod

class PropertyTaxCalculator(ABC):
    def __init__(self, property_value):
        self.property_value = property_value

    @abstractmethod
    def get_state_rate(self):
        pass

    def calculate_total_tax(self):
        # We can define concrete methods in an ABC!
        rate = self.get_state_rate()
        return self.property_value * rate

class CaliforniaTax(PropertyTaxCalculator):
    def get_state_rate(self):
        return 0.0072  # Approx 0.72%

class TexasTax(PropertyTaxCalculator):
    def get_state_rate(self):
        return 0.018  # Approx 1.8%

# Business Logic
homes = [
    CaliforniaTax(850000),
    TexasTax(350000)
]

for home in homes:
    state = "CA" if isinstance(home, CaliforniaTax) else "TX"
    print(f"Property in {state}: Value ${home.property_value:,} | Annual Tax: ${home.calculate_total_tax():,.2f}")

In this case, I defined the calculate_total_tax logic in the base class because the formula value * rate is universal, but I forced the subclasses to provide the rate.

I’ve found that using Abstract Base Classes is one of the fastest ways to level up from a Python scripter to a Software Engineer.

It forces you to think about the “What” before you dive into the “How.”

This architectural shift makes your code much easier to test and much harder for others to break.

If you are working on a project with more than two people, start using ABCs today. Your future self (and your teammates) will thank you when the project scales.

You may also like to read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.