Improving MRO and multiple heritage for objects

Given this code example, we can sometimes have a problem with some OOP in python.

class A:
    def __init__(self, arg1, arg2):
        ...

class B:
    def __init__(self, arg):
        ...

class C(A, B):
    def __init__(self):
        super().__init__(1,2) # will this crash ??

Obviously, the way that python handle multiple heritage isn’t always obvious, and can lead to many problems and misunderstandings. So I was wondering if there may be a way to make superclasses handling more explicit (because explicit is better than implicit). I don’t have any good ideas of how to improve this, but feel free to share any of your.

something like this?

class A:
    def __init__(self, arg1, arg2):
        print(arg1, arg2)
        self.A1 = arg1
        self.A2 = arg2

class B:
    def __init__(self, arg):
        print(arg)
        self.B = arg

class C(A, B):
    def __init__(self):
        A.__init__(self, 1, 2)
        B.__init__(self, 42)
        print(self.A1, self.A2, self.B)

C()

That works for me…?

2 Likes

I do agree, but when you use super(), it can be so confusing

Hello,

from my understanding, the super() built-in generic function may be used with single inheritance. Once you start employing multiple inheritance, then the explicit form as @Kurt demonstrated is preferred. This has to do with the way Python performs its search up the MRO tree, from left-to-right, and upwards. If you use the super() function for inheritance, then the order of the listed classes will be critical as the inheriting class will inherit the first one listed (left-most class). There are of course other disadvantages/pitfalls which are bit complex to describe them all here.

The book Learning Python, 5th Ed provides a very detailed explanation, starting on Ch. 32. I am sure you can look into other sources as well.

1 Like

I tend to use super() when there’s either just one superclass or the
left most superclass is effectively the only important superclass (eg
the rest are mixing and don’t need init calls).

In other situations I use Kurt’s approach and mostly don’t use
super().

So I basicly use super() to mean “the left most init without naming it
explicitly”, and call inits explicitly via their class otherwise.

The only real mix I use is when I accept some argument for one of the
additional classes:

 class C(A,B):
     def __init__(self, *a, b_arg, **kw):
         super().__init__(*a,**kw)
         B.__init__(self, b_arg)

and again this is the pattern: for me here, super() means A (the
left most superclass) without naming it. And I’m just pulling out a few
special cased arguments for use by some other inits.

Yes, instead of relying on super() you can explicitly call the base
class init methods.

class C(A, B):

 def __init__(self, arg1, arg2, arg3):
     A.__init__(self, arg1, arg2)
     B.__init__(self, arg3)

This is the way it was done in the old days before super() existed, and
it’s the way you still need to do it if the classes involved are not
designed for cooperative multiple inheritance.

It’s up to you to ensure there are no diamonds in the inheritance
hierarchy, and to keep the init methods up to date if the
inheritance hierarchy changes.