Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

Abstract Classes in Python: Enforcing Contracts with the abc Module

When you design a class hierarchy, sometimes you need to define what every subclass must implement — without providing the implementation yourself. That is the role of abstract classes. Python handles this through the abc module (Abstract Base Classes), which lets you define methods that subclasses are required to override.

The Problem Abstract Classes Solve

Without any enforcement, a subclass might “forget” to implement a method:

class Shape:
def area(self):
raise NotImplementedError("Subclasses must implement area()")
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
# forgot to implement area()
c = Circle(5)
c.area() # RuntimeError — but only at call time, not at creation time

The NotImplementedError approach works, but the error surfaces only when the method is actually called. You could create a Circle object, pass it around, and not discover the missing method until later.

Abstract classes catch this earlier — at instantiation time:

from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
"""Return the area of this shape."""
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
# still forgot area()
c = Circle(5) # TypeError: Can't instantiate abstract class Circle
# with abstract method area

The error happens the moment you try to create the object, not somewhere down the line.

Basic Usage

Inherit from ABC and mark abstract methods with @abstractmethod:

from abc import ABC, abstractmethod
import math
class Shape(ABC):
@abstractmethod
def area(self) -> float:
"""Return the area."""
@abstractmethod
def perimeter(self) -> float:
"""Return the perimeter."""
def describe(self):
"""Concrete method — shared by all subclasses."""
return (f"{type(self).__name__}: "
f"area={self.area():.2f}, perimeter={self.perimeter():.2f}")
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * 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)
shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
print(shape.describe())
# Circle: area=78.54, perimeter=31.42
# Rectangle: area=24.00, perimeter=20.00

Notice that Shape has a concrete method (describe) alongside abstract methods. Abstract classes can mix both — abstract methods define the contract, concrete methods provide shared behaviour.

Abstract Properties

Use @property combined with @abstractmethod to require subclasses to expose particular attributes through a property interface.

from abc import ABC, abstractmethod
class DatabaseAdapter(ABC):
@property
@abstractmethod
def connection_string(self) -> str:
"""Must return a valid connection string."""
@abstractmethod
def connect(self):
"""Establish the connection."""
@abstractmethod
def disconnect(self):
"""Close the connection."""
class PostgreSQLAdapter(DatabaseAdapter):
def __init__(self, host, port, dbname):
self.host = host
self.port = port
self.dbname = dbname
self._conn = None
@property
def connection_string(self):
return f"postgresql://{self.host}:{self.port}/{self.dbname}"
def connect(self):
print(f"Connecting: {self.connection_string}")
# self._conn = psycopg2.connect(...)
def disconnect(self):
print("Disconnecting from PostgreSQL")
# self._conn.close()
db = PostgreSQLAdapter("localhost", 5432, "myapp")
print(db.connection_string)
db.connect()

Simulating Interfaces

Python has no interface keyword. An interface in Python is simply an abstract class with only abstract methods and no concrete implementation.

from abc import ABC, abstractmethod
class Serialisable(ABC):
"""Interface: any class implementing this can be serialised."""
@abstractmethod
def to_dict(self) -> dict:
"""Convert object to a dictionary."""
@abstractmethod
def from_dict(self, data: dict):
"""Populate object from a dictionary."""
class JSONSerialisable(ABC):
"""Interface: any class implementing this can be serialised to JSON."""
@abstractmethod
def to_json(self) -> str:
"""Return a JSON string representation."""
class UserProfile(Serialisable, JSONSerialisable):
def __init__(self, username, email):
self.username = username
self.email = email
def to_dict(self):
return {"username": self.username, "email": self.email}
def from_dict(self, data):
self.username = data["username"]
self.email = data["email"]
def to_json(self):
import json
return json.dumps(self.to_dict())
profile = UserProfile("alice", "alice@example.com")
print(profile.to_json()) # {"username": "alice", "email": "alice@example.com"}

A class can inherit from multiple abstract classes (and therefore satisfy multiple “interface contracts”). Python’s MRO handles the resolution just as with regular classes.

Registering Virtual Subclasses

The abc module lets you register a class as a virtual subclass — it passes isinstance() checks without actually inheriting from the abstract class.

from abc import ABC
class Drawable(ABC):
pass
class ExternalWidget: # from a third-party library, can't modify it
def draw(self):
print("Drawing widget")
Drawable.register(ExternalWidget)
w = ExternalWidget()
print(isinstance(w, Drawable)) # True

This is useful for integrating with third-party code where you cannot modify the class hierarchy.

When to Use Abstract Classes

Use abstract classes when:

Do not use abstract classes when:

Common Mistakes

Using ABC without importing it. ABC must come from abc, not defined manually.

Forgetting @abstractmethod. A method defined inside an ABC subclass without @abstractmethod is treated as a regular concrete method. The subclass is not required to override it.

Implementing only some abstract methods. A subclass must implement every abstract method from all parent abstract classes. Missing even one keeps the class abstract, and trying to instantiate it raises TypeError.

Putting too much logic in the abstract class. Abstract classes work best as thin contracts. If you find yourself writing extensive logic in an abstract class, consider whether a mixin or a separate utility class would be clearer.