When I was trying Python multiple inheritance mechanism in my project, I encountered some pitfalls as is often said online. I implemented CLBackbone
class for conventional neural network backbones, and HATMaskBackbone
class for the ones with HAT mask mechanism, which is inherited from CLBackbone
. Now, I want a specific HATMaskMLP
which should be reasonably inherited from HATMaskBackbone
and MLP
, where MLP
is a subclass of CLBackbone
. Many problems arose here which making me so annoyed.
I make toy example codes to illustrate what I am saying in details.
class A:
def __init__(self, a):
self.a = a
class B(A):
def __init__(self, a, b):
super().__init__(a)
self.b = b
class C(A):
def __init__(self, a, c):
super().__init__(self, a)
self.c = c
class D(B, C):
def __init__(self, a, b, c, d):
# How to call its parent classes' __init__ methods?
self.d = d
This is usually referred to as diamond inheritance. Our question is shown in the comment of D
class. How to call its parent classes’ __init__
methods? How to make self.a = a
, self.b = b
, self.c = c
and self.d = d
all properly executed?
The natural way is to call super().__init__(...)
in D
class. However, there is ambiguity in which parent class’ __init__
method the super()
represents: B
, C
or even A
? If we look up online, many told us about the MRO (Method Resolution Order), and under this rule, the answer is B. Now the problem is we missed the C
class’ __init__
method!
After a long investigation on this, I found there is another explicit way to call the parent classes’ __init__
methods other than super()
. We can call them directly by B.__init__(self, a, b)
and C.__init__(self, a, c)
:
class D(B, C):
def __init__(self, a, b, c, d):
__init__(self, a, b)
B.__init__(self, a, c)
C.self.d = d
However, we still got a problem here, because of the super().__init__(a)
in the B.__init__(...)
method. If we call this in class D, the super()
will refer to D
‘s parent class, which is B
(discussed above), instead of A
that we expect. Therefore, we have to substitute the super()
s in every class with the explicit calls to the parent classes’ __init__
methods:
class A:
def __init__(self, a):
self.a = a
class B(A):
def __init__(self, a, b):
__init__(self, a)
A.self.b = b
class C(A):
def __init__(self, a, c):
__init__(self, a)
A.self.c = c
class D(B, C):
def __init__(self, a, b, c, d):
__init__(self, a, b)
B.__init__(self, a, c)
C.self.d = d
This solves the entire problem, but still not elegant. First, A.__init__(self)
was actually called twice, leading to redundancy. Second, we do all the calls manually. Even so, I have no choice but to use this way in my project. If anyone seeing this has better solutions, please let me know!