Creating Abstract Base Classes
(ABCs) and Interfaces
https://realpython.com/python-classes/#creating-abstract-base-classes-abcs-and-interfaces
by Leodanis Pozo Ramos Apr 26, 2023
Sometimes, you want to create a class hierarchy in which all the classes implement
a predefined interface. In other words, you want to define the specific set of public
methods and attributes that all the classes in the hierarchy must implement. In
Python, you can do this using what’s known as an abstract base class (ABC).
The abc module in the standard library exports a couple of ABCs and other related
tools that you can use to define custom base classes that require all their
subclasses to implement specific interfaces.
You can’t instantiate ABCs directly. You must subclass them. In a sense, ABCs work
as templates for other classes to inherit from.
To illustrate how to use Python’s ABCs, say that you want to create a class hierarchy
to represent different shapes, such as Circle, Square, and so on. You decide that all
the classes should have the get_area() and get_perimeter() methods. In this
situation, you can start with the following base class:
1 from abc import ABC, abstractmethod
2
3 class Shape(ABC):
4
5 @abstractmethod
6 def get_area(self):
7 pass
8
9 @abstractmethod
10 def get_perimeter(self):
11 pass
The Shape class inherits from abc.ABC, which means it’s an abstract base class.
Then you define the get_area() and get_perimeter() methods using the
@abstractmethod decorator. By using the @abstractmethod decorator, you declare
that these two methods are the common interface that all the subclasses of Shape
must implement. Note that the abstract methods do not have an implementation.
Now you can create the Circle class. Here’s the first approach to this class:
1 from abc import ABC, abstractmethod
2 from math import pi
3
4 class Shape(ABC):
5
6 @abstractmethod
7 def get_area(self):
8 pass
9
10 @abstractmethod
11 def get_perimeter(self):
12 pass
13
14 class Circle(Shape):
15 def __init__(self, radius):
16 self.__radius = radius
17
18 def get_area(self):
19 return pi * self.__radius ** 2
In this code snippet, you define the Circle class by inheriting from Shape. At this
point, you’ve added the get_area() method only. Now go ahead and run the
following code:
>>> from shapes_abc import Circle
>>> circle = Circle(100)
Traceback (most recent call last):
...
TypeError: Can't instantiate abstract class Circle
with abstract method get_perimeter
What just happened? You can’t instantiate Circle. That’s what ABCs are for. To be
able to instantiate Circle, you must provide suitable implementations for all its
abstract methods, which means you need to define a consistent interface:
1 from abc import ABC, abstractmethod
2 from math import pi
3
4 class Shape(ABC):
5 @abstractmethod
6 def get_area(self):
7 pass
8
9 @abstractmethod
10 def get_perimeter(self):
11 pass
12
13 class Circle(Shape):
14 def __init__(self, radius):
15 self.__radius = radius
16
17 def get_area(self):
18 return pi * self.__radius ** 2
19
20 def get_perimeter(self):
21 return 2 * pi * self.__radius
This time, your Circle class implements all the required methods. These methods
will be common to all the classes in your shape hierarchy. Once you’ve defined
suitable custom implementations for all the abstract methods, you can proceed to
instantiate Circle as in the following example:
>>> from shapes_abc import Circle
>>> circle = Circle(100)
>>> circle.get_area()
31415.926535897932
>>> circle.get_perimeter()
628.3185307179587
Once you’ve implemented custom methods to replace the abstract
implementations of get_area() and get_perimeter(), then you can instantiate and use
Circle in your code.
If you want to add a Square class to your shape hierarchy, then that class must have
custom implementations of the get_area() and get_perimeter() methods:
1 from abc import ABC, abstractmethod
2 from math import pi
3
4 class Shape(ABC):
5 @abstractmethod
6 def get_area(self):
7 pass
8
9 @abstractmethod
10 def get_perimeter(self):
11 pass
12
13 class Circle(Shape):
14 def __init__(self, radius):
15 self.__radius = radius
16
17 def get_area(self):
18 return pi * self.__radius ** 2
19
20 def get_perimeter(self):
21 return 2 * pi * self.__radius
22
23 class Square(Shape):
24 def __init__(self, side):
25 self.__side = side
26
27 def get_area(self):
28 return self.__side ** 2
29
30 def get_perimeter(self):
31 return 4 * self.__side
This example demonstrates how you can use ABCs to define a common interface
for a group of related classes. Each subclass must provide its own implementation
of the abstract methods in the base class.