Skip to main content
Python · lesson 15 of 34
Python beginner Lesson 15 of 34

Abstraction with Abstract Base Classes in Python

Use Python's abc module to define abstract base classes that force subclasses to implement required methods before they can be instantiated.

By SkillByExample · Updated

Abstraction means a base class states what subclasses must do, without dictating how. Python’s abc module enforces this: an abstract method must be overridden before a class can be instantiated. Output below is from Python 3.13.

Declaring Required Methods

ABC combined with @abstractmethod blocks instantiation of any class — base or subclass — that hasn’t implemented every abstract method.

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        ...

    @abstractmethod
    def perimeter(self) -> float:
        ...

    def describe(self) -> str:          # a concrete method - not every method must be abstract
        return f"Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}"

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        import math
        return math.pi * self.radius ** 2

    def perimeter(self):
        import math
        return 2 * math.pi * self.radius

try:
    Shape()
except TypeError as e:
    print(f"TypeError: {e}")

c = Circle(5)
print(c.describe())
$ python shapes.py
TypeError: Can't instantiate abstract class Shape without an implementation for abstract methods 'area', 'perimeter'
Area: 78.54, Perimeter: 31.42

Shape() fails immediately — Python checks for unimplemented abstract methods at instantiation time, not when they’re first called. describe() shows that a base class can mix abstract methods (area, perimeter) with a concrete one that every subclass inherits for free.

A Subclass That Forgets a Method

The check applies to subclasses too. Implementing only some of the abstract methods still leaves a class abstract.

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:
        ...

    @abstractmethod
    def perimeter(self) -> float:
        ...

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2
    # perimeter() is forgotten - the subclass is still abstract

try:
    Square(4)
except TypeError as e:
    print(f"TypeError: {e}")

print(sorted(Shape.__abstractmethods__))
print(sorted(Square.__abstractmethods__))
$ python incomplete_subclass.py
TypeError: Can't instantiate abstract class Square without an implementation for abstract method 'perimeter'
['area', 'perimeter']
['perimeter']

__abstractmethods__ is how Python tracks this — it’s a frozenset (wrapped in sorted() here, because set order can change from run to run) that shrinks as a subclass fills in implementations, and a class can only be instantiated once it’s empty. Square still lists perimeter, so instantiating it fails with the same TypeError as the empty base class.

Practice

1. Add a Rectangle subclass of Shape that implements both area and perimeter, then call describe() on it.

Solution
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

    @abstractmethod
    def perimeter(self) -> float: ...

    def describe(self) -> str:
        return f"Area: {self.area():.2f}, Perimeter: {self.perimeter():.2f}"

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)

print(Rectangle(3, 4).describe())
$ python rectangle.py
Area: 12.00, Perimeter: 14.00

With both abstract methods implemented, Rectangle.__abstractmethods__ is empty, so it instantiates and inherits describe() unchanged.

2. Add an abstract name property to Shape using @property stacked with @abstractmethod, and implement it in Circle.

Solution
import math
from abc import ABC, abstractmethod

class Shape(ABC):
    @property
    @abstractmethod
    def name(self) -> str: ...

    @abstractmethod
    def area(self) -> float: ...

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    @property
    def name(self):
        return "circle"

    def area(self):
        return math.pi * self.radius ** 2

c = Circle(2)
print(c.name, round(c.area(), 2))
print(sorted(Shape.__abstractmethods__))
$ python abstract_property.py
circle 12.57
['area', 'name']

@property must be the outer decorator so the abstract flag set by @abstractmethod lands on the getter. name then appears in __abstractmethods__ exactly like a regular method.

3. Assign Shape.area = lambda self: 0 after the class is defined, then attempt Shape() again. Does patching in a method after the fact satisfy the abstract-method check?

Solution
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

Shape.area = lambda self: 0
try:
    Shape()
except TypeError as e:
    print(f"TypeError: {e}")
print(sorted(Shape.__abstractmethods__))
$ python patched.py
TypeError: Can't instantiate abstract class Shape without an implementation for abstract method 'area'
['area']

Still fails. __abstractmethods__ is computed once, when the class is created, and is not re-checked when you patch the class later. (abc.update_abstractmethods(Shape), added in 3.10, recomputes it if you really need to.)

4. Without abc, write a “soft” abstract base class where an unimplemented method raises NotImplementedError only when called. Compare when the failure surfaces versus the ABC version.

Solution
class SoftShape:
    def area(self):
        raise NotImplementedError(f"{type(self).__name__} must implement area()")

s = SoftShape()       # succeeds: no instantiation-time check
print("created", type(s).__name__)
try:
    s.area()          # fails only now, when actually called
except NotImplementedError as e:
    print(f"NotImplementedError: {e}")
$ python soft_abstract.py
created SoftShape
NotImplementedError: SoftShape must implement area()

ABC catches the mistake at construction; the NotImplementedError convention only catches it when the missing method is actually used, which can be much later in a program’s run.

Frequently Asked Questions

What is abstraction in Python?
Abstraction means defining what a class must do without saying how — a base class declares required methods, and each subclass supplies its own implementation. Callers depend on the declared interface, not the implementation details.
How do I create an abstract class in Python?
Inherit from abc.ABC and mark required methods with the @abstractmethod decorator. Python then refuses to instantiate the class, or any subclass that hasn't implemented every abstract method, and raises TypeError instead.
Can an abstract base class have regular, non-abstract methods?
Yes. Only methods marked @abstractmethod must be overridden. A base class is free to mix required abstract methods with concrete ones that subclasses inherit as-is.
What's the difference between abstraction here and the ABCMeta content in the metaclasses lesson?
ABC and @abstractmethod, covered here, are what you use in everyday code to define an interface. The metaclasses lesson covers how ABCMeta itself works under the hood — useful for understanding the mechanism, not something you need to write abstract classes day to day.