Abstract Base Class (ABC)

Abstract Base Class (ABC)

TL;DR

  • Abstract Base Classes (ABCs) ensure that derived classes implement particular methods from the base class at instantiation time
  • Using Python’sabc module help avoid bugs and make class hierarchies easier to maintain.

In OOP, inheritance is very common. To make the code of inheritance relationship as maintainable and programmer-friendly as possible, we want to make sure that

  • instantiating the base class is impossible
  • forgetting to implement interface methods in one of the sub-classes raises an error as early as possible.

To enforce that a derived class implements a number of meth- ods from the base class, something like this Python idiom is typically used:

class Base:
    def foo(self):
        raise NotImplementedError()

    def bar(self):
        raise NotImplementedError()


class Concrete(Base):
    def foo(self):
        return "foo() called"

    # We forgot to override bar()...

Calling methods on an instance of Base correctly raises NotImplementedError exceptions:

>>> b = Base()
>>> b.foo() 
NotImplementedError

Furthermore, instantiating and using Concrete works as expected. If we call an unimplemented method like bar() on it, this also raises an exception:

>>> c = Concrete() 

>>> c.foo()
'foo() called'

>>> c.bar() 
NotImplementedError

This implementation is decent. However, it has two downsides

  • instantiate Base just fine without getting an error (which is not expected, as the base class should be uninstantiatable)
  • provide incomplete subclasses—instantiating Concrete will not raise an error until we call the missing method bar()

We can solve these issues with Python’s abc module. Here’s an updated implemen- tation using an Abstract Base Class defined with the abc module:

from abc import ABCMeta, abstractmethod

class Base(metaclass=ABCMeta):
    @abstractmethod
    def foo(self):
        pass

    @abstractmethod
    def bar(self):
        pass
class Concrete(Base):
    def foo(self):
        pass

    # We forget to declare bar()

This still behaves as expected and creates the correct class hierarchy:

>>> issubclass(Concrete, Base)
True

Benefit of applying the abc module:

  • Instantiating base class will raise an exception

    >>> b = Base()
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    Cell In [19], line 1
    ----> 1 b = Base()
    
    TypeError: Can't instantiate abstract class Base with abstract methods bar, foo
    
  • Subclasses of Base raise a TypeError at instantiation time whenever we forget to im- plement any abstract methods. The raised exception also tells us which method or methods we’re missing, makes it more difficult to write invalid subclasses.

    >>> c = Concrete()
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    Cell In [10], line 1
    ----> 1 c = Concrete()
    
    TypeError: Can't instantiate abstract class Concrete with abstract method bar
    

These advantages often make the class hierarchies more robust and more readily maintainable. Using ABCs states the programmer’s intent clearly and thus makes the code more communicative. 👍