An abstract class is a class that can’t be instantiated directly. It exists only to define a contract — “if you’re my subclass, you must implement these methods.” Think of it like a job description — it says what needs to be done, but doesn’t do the work itself.
Why Do We Need Abstract Classes?
Without them, we have no way to enforce that subclasses implement certain methods. We’d only find out at runtime when calling a missing method.
class Shape:
def area(self):
pass # subclasses "should" override this, but nothing forces them
class Circle(Shape):
pass # oops, forgot to implement area()
c = Circle()
c.area() # returns None — no error, just a silent bug
Abstract classes fix this by raising an error at instantiation time, not when the method is called.
abc.ABC and @abstractmethod
We use the abc module (Abstract Base Classes) to create abstract classes.
from abc import ABC, abstractmethod
class Shape(ABC): # inherit from ABC
@abstractmethod
def area(self):
"""Subclasses must implement this."""
pass
@abstractmethod
def perimeter(self):
pass
# Shape() # TypeError: Can't instantiate abstract class Shape
Now any subclass must implement both area() and perimeter(), or it’s also abstract and can’t be instantiated.
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
c = Circle(5) # works — all abstract methods are implemented
print(c.area()) # 78.53975
If we forget one:
class BadCircle(Shape):
def area(self):
return 0
# forgot perimeter!
# BadCircle() # TypeError: Can't instantiate abstract class BadCircle
# # with abstract method perimeter
The error message tells us exactly which methods we missed. Very helpful.
Abstract Properties
We can combine @abstractmethod with @property to require subclasses to implement properties.
from abc import ABC, abstractmethod
class Vehicle(ABC):
@property
@abstractmethod
def fuel_type(self):
pass
class Car(Vehicle):
@property
def fuel_type(self):
return "Gasoline"
print(Car().fuel_type) # Gasoline
The order matters: @property goes above @abstractmethod.
Concrete Methods in Abstract Classes
Abstract classes can have regular (concrete) methods too. These provide shared behavior that subclasses inherit for free.
class Shape(ABC):
@abstractmethod
def area(self):
pass
def describe(self): # concrete — all subclasses get this
return f"I'm a shape with area {self.area():.2f}"
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
print(Square(4).describe()) # I'm a shape with area 16.00
ABCMeta — The Metaclass Behind ABC
ABC is just a convenience class that uses ABCMeta as its metaclass. These two are equivalent:
# Style 1: inherit from ABC
class Shape(ABC):
pass
# Style 2: use ABCMeta directly
class Shape(metaclass=ABCMeta):
pass
We almost always use ABC unless we need to combine with another metaclass.
Virtual Subclasses with register()
Sometimes we want to say “this class satisfies the interface” without actual inheritance. register() creates a virtual subclass.
from abc import ABC, abstractmethod
class Drawable(ABC):
@abstractmethod
def draw(self):
pass
class ThirdPartyWidget: # we can't modify this class
def draw(self):
print("Drawing widget")
Drawable.register(ThirdPartyWidget)
print(isinstance(ThirdPartyWidget(), Drawable)) # True
print(issubclass(ThirdPartyWidget, Drawable)) # True
But be careful — register() doesn’t actually check that ThirdPartyWidget implements draw(). It’s a declaration of intent, not enforcement.
ABC vs Duck Typing vs Protocol
Python gives us three ways to define “interfaces”:
ABC (nominal): “You must inherit from me.” Strict, explicit, enforced at instantiation. Good for frameworks where we need guarantees.
Duck typing: “If it has the right methods, it works.” No formal contract. The simplest approach, and the most Pythonic for day-to-day code.
typing.Protocol (structural): “If it has the right methods, the type checker is happy.” Combines duck typing’s flexibility with static analysis. No inheritance needed.
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None: ... # just the signature
class Circle:
def draw(self) -> None: # no inheritance needed
print("Drawing circle")
def render(obj: Drawable) -> None: # type checker validates this
obj.draw()
render(Circle()) # works — Circle matches the protocol structurally
Protocols are covered in detail in the Protocols & Structural Subtyping topic.
When to Use ABC
- When we’re building a framework and need to guarantee subclasses implement certain methods.
- When we want early failure — errors at instantiation, not deep in runtime.
- When using
isinstance()checks against a shared base class.
For most application code, duck typing or Protocol is simpler and more flexible. ABCs shine in library and framework design.
In simple language, abstract classes are contracts that say “implement these methods or you can’t be instantiated.” Python’s abc.ABC and @abstractmethod enforce this contract at creation time. For lighter-weight contracts, duck typing and typing.Protocol are often better fits.