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.

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.

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.

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:
- Don’t Over-Engineer: If you only have two classes that are very different, you probably don’t need an ABC.
- Keep Interfaces Small: I find it’s better to have several small ABCs (e.g., Taxable, Insured) than one giant BusinessEntity class.
- 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:
- How to Use Python slots to Optimize Memory Performance
- Data Encapsulation in Python
- Python @property vs Normal Methods
- How to Use @property Decorator in Python

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.