Search code examples
pythoninheritancedesign-patternsmultiple-inheritance

In python multiple inheritance, is it possible for one parent to access another parent without knowing what that parent is?


In my code, I have the inheritance relation B -> C -> D, and there is a particular function f of interest here

class C(B):
    def f(self):
        if <some condition>:
            B.f(self)
        else:
            <do other stuff>

And the issue is that there are multiple possibilities of B in reality, but the logic of C is the same. A straightforward way is then to use linear inheritance multiple times, i.e.,

B1 -> C1 -> D1
B2 -> C2 -> D2
B3 -> C3 -> D3

However, it is wasteful. Then I am thinking of using multiple inheritance

D1(C, B1)
D2(C, B2)
D3(C, B3)

Then the problem is how does C.f() access B1.f() (same for B2, B3) since it only knows the existence of B1 via D1?

Is this a bad idea to use multiple inheritance in this case? Or is there a better way than both the straightforward linear inheritance with multiple Cs and multiple inheritance (if it works)?


Solution

  • That is a classic problem on multiple-inheritance that ultimately led some languages to opt-out from multiple-inheritance at all.

    Python however, got away just by putting in place a cleverly crafted "method resolution order" (mro) algorithm, and later, with the advent of the super built-in call. The algorithm and its history are detailed in https://www.python.org/download/releases/2.3/mro/

    But, long history short, it just works as expected "by heart". For example, by pasting this code on the interactive interpreter:

    class A: pass
    class B(A): 
        def f(self): raise NotImplementedError
    class C(B):
        def f(self):
            super().f()
            print("C")
    class B1(B): f = lambda s: print("B1")
    class B2(B): f = lambda s: print("B2")
    class D(C, B1): pass
    class D1(C, B2): pass
    

    We get these results when tinkering along:

    In [25]: D().f()
    B1
    C
    
    In [26]: D1().f()
    B2
    C
    
    In [27]: D.__mro__
    Out[27]: (__main__.D, __main__.C, __main__.B1, __main__.B, __main__.A, object)