Home > Software engineering >  Understanding difference between parent class init() and super.__init__
Understanding difference between parent class init() and super.__init__

Time:09-07

I looked at answers https://stackoverflow.com/a/33469090/11638153 and https://stackoverflow.com/a/27134600 but did not understand what is "indirection." To be specific, if I have classes like below, are there any drawbacks or advantages in hardcoding the parent class __init__(), as done in class Child1 (why do we need super() when one can explicitly write __init__() methods to invoke)?

class Base1:
    def __init__(self):
        print("Class Base1 init()")
        
class Base2:
    def __init__(self):
        print("Class Base2 init()")

class Child1(Base1, Base2):
    def __init__(self):
        print("Class Child1 init()")
        Base1.__init__(self)
        Base2.__init__(self)
        
class Child2(Base1, Base2):
    def __init__(self):
        print("Class Child2 init()")
        super().__init__()

if __name__ == "__main__":
    obj1 = Child1()
    print("---")
    obj2 = Child2()

CodePudding user response:

Roughly, the difference is that ParentClass.__init__(self) always calls the specifically named class while super().__init__() makes a runtime computation to determine which class to call.

In many situations, the two ways give the same results. However, super() is much more powerful. It a multiple inheritance scenario, it can potentially call some class other than the parent of the current class. This article covers the latter scenario is detail.

For single inheritance, the reason to prefer super() is that that it read nicely and that it makes maintenance easier by computing the parent class.

For multiple inheritance, super() is the only straightforward way to access parent classes in the order of the MRO for the class of the calling instance. If those words don't mean anything to you, please look at the referenced article.

CodePudding user response:

In your example code, Child1's design is very fragile. If its two base classes share some common base (and call their own parents' __init__ method inside of their own turn), that grandparent class might get initialized more than once, which would be bad if it's computationally costly, or has side effects. This is the "diamond pattern" of inheritance hierarchy that can be troublesome in many languages (like C ) that allow multiple inheritance.

Here's an example of the bad problem that can come up with explicit parent calls, and which super() is designed to fix:

class Grandparent:
    def __init__(self):
        print("Grandparent")

class Parent1(Grandparent):
    def __init__(self):
        print("Parent1")
        Grandparent.__init__(self)

class Parent2(Grandparent):
    def __init__(self):
        print("Parent2")
        Grandparent.__init__(self)

class Child(Parent1, Parent2):
    def __init__(self):
        print("Child")
        Parent1.__init__(self)
        Parent2.__init__(self)

c = Child() # this causes "Grandparent" to be printed twice!

Python's solution is to use super() which allows collaborative multiple inheritance. Each class must be set up to use it, including the intermediate base classes! Once you set everything up to use super, they'll call each other as necessary, and nothing will ever get called more than once.

class Grandparent:
    def __init__(self):
        print("Grandparent")

class Parent1(Grandparent):
    def __init__(self):
        print("Parent1")
        super().__init__()

class Parent2(Grandparent):
    def __init__(self):
        print("Parent2")
        super().__init__()

class Child(Parent1, Parent2):
    def __init__(self):
        print("Child")
        super().__init__()

c = Child() # no extra "Grandparent" printout this time

In fact, every instance of multiple-inheritance in Python is a diamond shape, with the Grandparent class of object (if not something else). The object class has an __init__ method that takes no arguments (other than self) and does nothing.

It's important to note that the super().__init__ calls from the ParentX classes don't always go directly to Grandparent. When initializing an instance of Parent1 or Parent2, they will, but when instantiating an instance of Child, then Parent1.__init__'s call will go to Parent2.__init__, and only from there will Parent2's super call go to Grandparent.__init__.

Where a super call resolves to depends on the MRO (Method Resolution Order) of the instance the methods are being called on. A super call generally means "find this method in the next class in the MRO" relative to where it's called from. You can see the MRO of a class by calling SomeClass.mro(). For Child in my examples, it's: [Child, Parent1, Parent2, Grandparent, object]

  • Related