How to inherit from ABC and another class

Viewed 846

I have a class

class A(ABC):
  @abstractmethod
  def m():
    pass

and a bunch of classes inheriting from it.

class M1(A):
  ...

class M2(A):
  ...

...

Some of these have a lot in common as they share a lot of functionality such that I would like to introduce a common base class which itself inherits from A, but it does not introduce any new @abstractmethod methods:


       A
       |
  +----+----+
  |    |    |
  M1   M2   B
            |
         +--+--+
         |     |
         K1   K2

What should the definition for class B look like?

I'm on Python 3.8

EDIT: I am specifically asking as PyCharm tells me "B must inherit all abstract methods"

2 Answers

You can simply use

class B(A):
   pass

or any other code that is common to all children, but without m. This will still be abstract due to the absence of m.


An alternative is to use

class B(A, ABC):
   pass

In my opinion this is less good. This states that B is both an A and an ABC, while the former alternative states that B is an A, and it is an ABC insofar as much as A is an ABC itself.

Consider the case where you decide later on that A is not an ABC after all. For M1 and M2, no change needs to be made. For B, you now need to remove its being an ABC as well. While making sure that A and B are synchronized in subclassing ABC is not a huge overhead in terms of code maintenance, I think it does indicate that the design is problematic, PyCharm's warning about the former alternative notwithstanding (I would suppress it as a false-positive).

In your specific case you don't need to mix in anything. I don't personally use Pycharm, but creating B as a subclass of A works just fine and python won't complain.

>>> from abc import ABC, abstractmethod
>>> class A(ABC):
...     @abstractmethod
...     def m(self):
...         pass
... 
>>> class B(A):
...     def common_method(self) -> str:
...         return 'b'
... 
>>> class K1(B):
...     def m(self) -> str:
...         return 'k1'
... 
>>> class K2(B):
...     def m(self) -> str:
...         return 'k2'
... 
>>> k1 = K1()
>>> k2 = K2()
>>> k1.m()
'k1'
>>> k2.m()
'k2'
>>> k1.common_method()
'b'
>>> k2.common_method()
'b'
>>> # However you can't do this
>>> b = B()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class B with abstract methods m

In case you have a different but similar structure (not the question directly but still relevant)

       A        <----- this is not abstract
       |
  +----+----+
  |    |    |
  M1   M2   B   <------ you want this to be abstract and also group some
            |           functionality for K1 and K2
         +--+--+
         |     |
         K1   K2

You can do it without mixing classes by defining the metaclass instead of subclassing ABC and A as a mixin (the metaclass does all the work anyways). Here is how you can do it.

>>> from abc import ABCMeta, abstractmethod
>>> class A:
...     def m(self) -> str:
...         return 'a'
... 
>>> class B(A, metaclass=ABCMeta):
...     @abstractmethod
...     def n(self):
...         pass
...     def common_method(self) -> str:
...         return 'b'
... 
>>> class K1(B):
...     def n(self) -> str:
...         return 'k1'
... 
>>> class K2(B):
...     def n(self) -> str:
...         return 'k2'
... 
>>> k1 = K1()
>>> k2 = K2()
>>> k1.m()
'a'
>>> k2.m()
'a'
>>> k1.n()
'k1'
>>> k2.n()
'k2'
>>> k1.common_method()
'b'
>>> k2.common_method()
'b'
>>>
Related