Search code examples
pythoninheritancesuperdiamond-problem

Python calling base class using super vs static


Everywhere I look everyone keeps saying how great super() is. But, I'm leaning toward not using super() because it makes everything much more complicated than I would like. I've seen some popular examples of using super() but they never seem to show passing positional required arguments into the base class constructors.

I know of the diamond problem in Python, and super() prevents calling the base_base class twice. (in this case Class A)

Is it so bad that Class A's constructor is called twice? (i.e., Case 2)

Below is my code for the two cases.

Case 1: Using super()._ init _()

#abstractclass
class A(object):

    #abstractmethod
    def __init__(self, for_a, *args):
        self.val_a = for_a

#abstractclass
class B(A):

    #abstractmethod
    def __init__(self, for_a, for_b, for_c):
        super().__init__(for_a, for_c)
        self.val_b = for_b

#abstractclass
class C(A):

    #abstractmethod
    def __init__(self, for_a, for_c):
        super().__init__(for_a)
        self.val_c = for_c

class D(B, C):

    def __init__(self, for_a, for_b, for_c, for_d):
        super().__init__(for_a, for_b, for_c)
        self.val_d = for_d

class E(B):

    def __init__(self, for_a, for_b, for_e, *args):
        super().__init__(for_a, for_b, *args)
        self.val_e = for_e

newobject1 = D(1, 2, 3, 4)

newobject2 = E(10, 11, 12, 0)

Case 2: Static - Using base._ init _(self)

#abstractclass
class A(object):

    #abstractmethod
    def __init__(self, for_a):
        self.val_a = for_a

#abstractclass
class B(A):

    #abstractmethod
    def __init__(self, for_a, for_b):
        A.__init__(self, for_a)
        self.val_b = for_b

#abstractclass
class C(A):

    #abstractmethod
    def __init__(self, for_a, for_c):
        A.__init__(self, for_a)
        self.val_c = for_c

class D(B, C):

    def __init__(self, for_a, for_b, for_c, for_d):
        B.__init__(self, for_a, for_b)
        C.__init__(self, for_a, for_c)
        self.val_d = for_d

class E(B):

    def __init__(self, for_a, for_b, for_e):
        super().__init__(for_a, for_b)
        self.val_e = for_e

newobject1 = D(1, 2, 3, 4)

newobject2 = E(10, 11, 12)

Solution

  • Is it so bad that Class A's constructor is called twice?

    Absolutely, if the constructor is slow, or if it's non-idempotent, or if it acquires resources like file handles or database connections. In a more complex inheritance structure, you might also get constructors called 4 times, or 6, or as many times as there are paths up the inheritance graph from the child to the ancestor.

    I'm leaning toward not using super() because it makes everything much more complicated than I would like

    It's not super making things complicated - it's multiple inheritance. Avoiding super in multiple inheritance just gives you different, probably-worse problems than super's problems.

    I've seen some popular examples of using super() but they never seem to show passing positional required arguments into the base class constructors.

    That's because positional constructor arguments don't work well with multiple inheritance. If you want to use a multiple inheritance structure like this, try making the arguments keyword-only and using **kwargs to pass through unrecognized arguments for other classes to handle. "Python's super() considered super" has some good advice for how to make it work.