One of the simplest and most common ways you’ll see Abstract Base Classes (or ABCs) be used is the combination of the ABC class and abstractmethod that Python provides in its abc module. Let’s use those:

from abc import ABC, abstractmethod
 
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
 
    @abstractmethod
    def perimeter(self):
        pass

The reason why we can just have pass for both area and perimeter is because these don’t actually do anything in this class definition, but what we’re telling Python is that, in order for a subclass of Shape to be valid, it has to overwrite area and perimeter methods. So, if we were to implement a child class that doesn’t override those, it can’t be actually be created (Python raise an error). Let’s demonstrate it:

class Cirlce(Shape):
    def __init__(self, radius) -> None:
        self.radius = radius
 
    def area(self):
        return 3.14 * self.radius ** 2
 
    def perimeter(self):
        return 2 * 3.14 * self.radius

At this point we have a valid subclass Circle:

c1 = Cirlce(5)
 
>>> c1.area()
78.5
>>> c1.perimeter()
31.400000000000002

What if we don’t create a valid sublcass? Let’s create another class:

class Triangle(Shape):
    def __init__(self, base, height, sides) -> None:
        self.base = base
        self.height = height
        self.sides = sides
 
    def perimeter(self):
        return sum(self.sides)

Then:

>>> t1 = Triangle(5, 13, [3,4,5])
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

Cell In[5], line 10
	  7     def perimeter(self):
	  8         return sum(self.sides)
---> 10 t1 = Triangle(5, 13, [3,4,5])


TypeError: Can't instantiate abstract class Triangle with abstract method area

Because we didn’t override the area method, it gives you an error. This is one of the big values of abstract classes, especially if you are a library author; you can define the minimum amount of funcionality that needs to be defined on child classes for them to be considered valid child classes. Let’s fix this class:

class Triangle(Shape):
    def __init__(self, base, height, sides) -> None:
        self.base = base
        self.height = height
        self.sides = sides
 
    def perimeter(self):
        return sum(self.sides)
    
    def area(self):
        return 0.5 * self.base * self.height

Then:

>>> t1 = Triangle(5, 13, [3,4,5])

>>> t1.perimeter()
12
>>> t1.area()
32.5

How exactly is this working? In the lesson about metaclasses (TODO: inserisci link), near the end we defined a class that was effectively the reverse version of an abstract base class and in that example if a child class tried to override a method that was defined on the parent, it would stop it from being created. What Python does is it has ABC, which has a metaclass defined of ABC.meta and that metaclass will check through and see if there’s any abtract methods that are still in the class that’s trying to be instantiated and, if they are, it’s going to raise that TypeError. For the very same reason, we can’t create an instance of Shape because it has two of these abstract methofs intact. On the other hand, the reason why Circle and Triangle can be instantiated is because they’re overriding the definition that’s provided on their parent.

(The following part talks about more complex examples, but it involves metaclass, that I didn’t study yet. So, I’ll come back on this video as soon as I study that topic)

Let’s give a more complex example. We can do many interesting things with abstract base classes, especially if you’re trying to write some type of plug-in interface. Let’s first create the class and then we give some explanations:

import abc
 
class ServicePlugin(metaclass=abc.ABCMeta):
    plugins = {}

We’re going to create class ServicePlugin and we specify that it’s a metaclass. This helps to collapse the inheritance tree a little bit more where, instead of having ABC as a parent class of whatever your plug-in systems is, you can just have ServicePlugin be the top level (of course below object). I want ServicePlugin to be the primary interface for the plugins in the application and, in order to do that, we’re going to kkep track of whatever plugins are available by creating a class level attribute plugins

TODO: to finish this lesson!