Why do we need the abstract base class library when we have NotImplementedError?

Here's a typical use of abstract base classes in python:

from abc import ABC

class MyBaseClass(ABC):
    @abstractmethod
    def __init__(self):
        print("Here's some logic that I want all subclasses to have")

class ChildClass(MyBaseClass):
    def __init__(self):
        super().__init__()
        print("Here's some additional logic that I want this subclass to have")

The behavior is: it's not possible to instantiate MyBaseClass, but it is possible to subclass it, and MyBaseClass includes logic inherited by all subclasses.

Here's another way to get this same behavior:

class MyBaseClass:
    def __init__(self):
        if type(self) == MyBaseClass:
            raise NotImplementedError
        print("Here's some logic that I want all subclasses to have")

class ChildClass(MyBaseClass):
    def __init__(self):
        super().__init__()
        print("Here's some additional logic that I want this subclass to have")

As far as I can tell, this approach has the exact same behavior, has the same amount of code, is equally explicit, and uses only native Python. So what does ABC do that this approach can't handle?


Abstract base classes prevent you from instantiating an object unless you implement the abstract methods. This raises the error immediately and alerts you to the fact that you need to implement the method. This is especially helpful if you instantiate the class but don't call the method until after a lengthy execution process: you won't raise NotImplementedError until you call the method.

import abc

class AbstractClass(abc.ABC):
    @abc.abstractmethod
    def test(self):
        pass

class NotAbstractClass:
    def test(self):
        raise NotImplementedError()

class AbstractInheritor(AbstractClass):
    pass

class NotAbstractInheritor(NotAbstractClass):
    pass

x = AbstractInheritor() # raises error immediately
y = NotAbstractInheritor() # no error despite not implementing test

In fact, the line where I create x is caught by my type checker, so I do not even need to run the code to know there is a problem. Meanwhile, y is perfectly fine.