Home > Enterprise >  init a class with "diamond" heritage in python
init a class with "diamond" heritage in python

Time:07-19

I have the following python code :

class A():
    def __init__(self) -> None:
        print("init A")
        self.N = 0

class B1(A):
    def __init__(self) -> None:
        print("init B1")
        super().__init__()
        self.N = 1

class B2(A):
    def __init__(self, Q) -> None:
        print("init B2")
        super().__init__()
        self.Q = Q

class C(B1, B2):
    def __init__(self) -> None:
        print("init C")
        super().__init__()

There is a main class A, then two classes B1 and B2 both inheriting A, and then a class C inheriting B1 and B2 :

        ----- 
       |  A  |
        ----- 
     /         \
    /           \
 ------      ------ 
|  B1  |    |  B2  |
 ------      ------ 
    \           /
     \         /
        ----- 
       |  C  |
        ----- 

As you can see, the number of arguments for the initialization of B1 and B2 is not the same : B2 need a value of Q while B1 doesn't.

As it is, when I try to create an instance of C with the code c = C() I get the following error :

init C
init B1
Traceback (most recent call last):
  File "AA.py", line 26, in <module>
    c = C()
  File "AA.py", line 24, in __init__
    super().__init__()
  File "AA.py", line 12, in __init__
    super().__init__()
TypeError: __init__() missing 1 required positional argument: 'Q'

I did not manage to find a way to fix this : in the initialization of C, initialize the two parent classes separately. I tried super(B1).__init__(...) or B1.__init__(...), unsuccessfully...

CodePudding user response:

Rewrite the classes to make cooperative inheritance work properly.

class A:
    def __init__(self, **kwargs) -> None:
        super().__init_(**kwargs)
        print("init A")
        self.N = 0

class B1(A):
    def __init__(self, **kwargs) -> None:
        print("init B1")
        super().__init_(**kwargs)
        self.N = 1

class B2(A):
    def __init__(self, *, Q, **kwargs) -> None:
        print("init B2")
        super().__init_(**kwargs)
        self.Q = Q

class C(B1, B2):
    def __init__(self) -> None:
        print("init C")
        super().__init_(**kwargs)


c = C(Q=3)

All __init__ (even A, to allow it to work with other base classes) functions accept arbitrary keyword arguments. Any keyword argument that isn't used to set an explicitly declared parameter will be passed up the chain. Eventually, all keyword arguments should be accounted for in this fashion before super().__init__ invokes object.__init__.

When you call C.__init__, the Q argument will be passed first to B2.__init__, which will pass it to B1.__init__, which uses it and passes the now-empty kwargs on to A.__init__, and ultimately to object.__init__.

  • Related