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’s
abc
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 methodbar()
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. 👍