Multiple sequential abstract base classes in Python 3.4+

P. *_*Lau 7 python abstract-class python-3.x

Take a common vehicle inheritance example where Vehicle and Car are ABCs and the latter inherits from the former. We then have e.g. FordMustang which is not abstract and should inherit from Car.

So, we have

class Vehicle(ABC):

    @abstractmethod
    def abstract_vehicle_method(self):
        pass
Run Code Online (Sandbox Code Playgroud)

Which is presumably all fine and dandy. However, for the Car class, I am not sure whether it should inherit only from Vehicle or from Vehicle and ABC, and if so, in what order? Moreover, I would like to force abstract_vehicle_method() to be defined in FordMustang and all such non-abstract classes, so should I repeat the abstract_vehicle_method definition in Car or will the inheritance sort this out?

To enumerate the options:

Should it be

class Car(Vehicle):
    [...]
Run Code Online (Sandbox Code Playgroud)

or

class Car(ABC, Vehicle):
    [...]
Run Code Online (Sandbox Code Playgroud)

or

class Car(Vehicle, ABC):
    [...]
Run Code Online (Sandbox Code Playgroud)

and (assuming the first is correct for simplicity) should it be

class Vehicle(ABC):

    @abstractmethod
    def abstract_vehicle_method(self):
        pass

class Car(Vehicle):

    @abstractmethod
    def abstract_vehicle_method(self):
        pass

    # Some car specific defs here

class FordMustang(Car):

    def abstract_vehicle_method(self):
        # Concrete def here
Run Code Online (Sandbox Code Playgroud)

or just

class Vehicle(ABC):

    @abstractmethod
    def abstract_vehicle_method(self):
        pass

class Car(Vehicle):

    # Some car specific defs here

class FordMustang(Car):

    def abstract_vehicle_method(self):
        # Concrete def here
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 6

You can directly inherit from Vehicle; the metaclass that makes Vehicle 'abstract' is inherited along with it. You do not need to mix in ABC for those subclasses.

You also don't need to re-define the method. It is inherited too, along with it's 'abstractness'.

Under the hood, the metaclass tracks what attributes are 'abstract'; as long as there are any, the class can't be used to create instances. You can add more abstract methods, or by providing a different attribute for the same name, remove abstract methods from the set.

Demo for your specific example:

>>> from abc import ABC, abstractmethod
>>> class Vehicle(ABC):
...     @abstractmethod
...     def abstract_vehicle_method(self):
...         pass
...
>>> type(Vehicle)  # it's an abstract class
<class 'abc.ABCMeta'>
>>> Vehicle.__abstractmethods__  # this set determines what is still abstract
frozenset({'abstract_vehicle_method'})
>>> class Car(Vehicle):
...     pass
...
>>> type(Car)  # still an abstract class
<class 'abc.ABCMeta'>
>>> Car.__abstractmethods__  # still has abstract methods, incl. inherited methods
frozenset({'abstract_vehicle_method'})
>>> class FordMustang(Car):
...     def abstract_vehicle_method(self):
...         pass
...
>>> type(FordMustang)  # still an abstract class
<class 'abc.ABCMeta'>
>>> FordMustang.__abstractmethods__  # but with no abstract methods left
frozenset()
>>> FordMustang()  # so we can create an instance
<__main__.FordMustang object at 0x106054f98>
Run Code Online (Sandbox Code Playgroud)