41

Is there a difference between using super() and using the parent class name directly? For example:

class Parent:
    def __init__(self):
        print("In parent")
        self.__a=10

class Child(Parent):
    def __init__(self):
        super().__init__()     # using super()
        Parent.__init__(self)  # using Parent class name

c=Child()

Is there internally a difference between super().__init__() and Parent.__init__(self)?

Stevoisiak
  • 23,794
  • 27
  • 122
  • 225
codingsplash
  • 4,785
  • 12
  • 51
  • 90
  • `super()` resolves the call using the **MRO**... – Willem Van Onsem Feb 23 '17 at 10:53
  • Only different if your subclass has multiple superclasses, or if you might at some point change your subclass to inherit from a different superclass. Btw, "parent class" is not usual terminology for inheritance. – khelwood Feb 23 '17 at 10:53
  • 1
    @khelwood: strictly speaking that is not true: if you subclass a class that has multiple inheritance, your `super()` can in fact delegate to a slibing, not a parent. It simply returns the next in line of the `__mro__`. – Willem Van Onsem Feb 23 '17 at 11:01
  • Possible duplicate of [Difference between super() and calling superclass directly](https://stackoverflow.com/questions/21639788/difference-between-super-and-calling-superclass-directly) – Stevoisiak Feb 23 '18 at 16:51

2 Answers2

23

Not in this case. But in general, and especially when you use multiple inheritance, super() delegates to the next object in the Method Resolution Order (MRO) as is specified in the documentation:

super([type[, object-or-type]])

Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class. The search order is same as that used by getattr() except that the type itself is skipped.

The __mro__ attribute of the type lists the method resolution search order used by both getattr() and super(). The attribute is dynamic and can change whenever the inheritance hierarchy is updated.

(...)

(copied, boldface added)

Say for instance you define classes like (borrowed from this question, where the MRO is discussed in more detail):

class F:
    def __init__(self):
        print('F%s'%super().__init__)
        super().__init__()

class G: 
    def __init__(self):
        print('G%s'%super().__init__)
        super().__init__() 

class H: 
    def __init__(self):
        print('H%s'%super().__init__)
        super().__init__()

class E(G,H):
    def __init__(self):
        print('E%s'%super().__init__)
        super().__init__()

class D(E,F): 
    def __init__(self):
        print('D%s'%super().__init__)
        super().__init__() 

class C(E,G): 
    def __init__(self):
        print('C%s'%super().__init__)
        super().__init__()

class B(C,H): 
    def __init__(self):
        print('B%s'%super().__init__)
        super().__init__()

class A(D,B,E): 
    def __init__(self):
        print('A%s'%super().__init__)
        super().__init__()

Then the __mro__ of A is:

A.__mro__ == (A,D,B,C,E,G,H,F,object)

Now if we call A(), it prints:

A<bound method D.__init__ of <__main__.A object at 0x7efefd8645c0>>
D<bound method B.__init__ of <__main__.A object at 0x7efefd8645c0>>
B<bound method C.__init__ of <__main__.A object at 0x7efefd8645c0>>
C<bound method E.__init__ of <__main__.A object at 0x7efefd8645c0>>
E<bound method G.__init__ of <__main__.A object at 0x7efefd8645c0>>
G<bound method H.__init__ of <__main__.A object at 0x7efefd8645c0>>
H<bound method F.__init__ of <__main__.A object at 0x7efefd8645c0>>
F<method-wrapper '__init__' of A object at 0x7efefd8645c0>
<__main__.A object at 0x7efefd8645c0>

so it means that in the context of A and when trying to obtain __init__ that:

  • super().__init__ of A is D.__init__;
  • super().__init__ of D is B.__init__;
  • super().__init__ of B is C.__init__;
  • super().__init__ of C is E.__init__;
  • super().__init__ of E is G.__init__;
  • super().__init__ of G is H.__init__;
  • super().__init__ of H is F.__init__; and
  • super().__init__ of F is object.__init__.

Note thus that super() does not per se delegates to a parent. For instance the super() of D is B and B is not a superclass of D, so it really depends on the type of the object (not on the class).

Now in case of D, the __mro__ is:

D.__mro__ = (D,E,G,H,F,object)

If we construct a D however we get:

D<bound method E.__init__ of <__main__.D object at 0x7efefd864630>>
E<bound method G.__init__ of <__main__.D object at 0x7efefd864630>>
G<bound method H.__init__ of <__main__.D object at 0x7efefd864630>>
H<bound method F.__init__ of <__main__.D object at 0x7efefd864630>>
F<method-wrapper '__init__' of D object at 0x7efefd864630>

So in the context of D it holds that:

  • super().__init__ of D is E.__init__;
  • super().__init__ of E is G.__init__;
  • super().__init__ of G is H.__init__;
  • super().__init__ of H is F.__init__; and
  • super().__init__ of F is object.__init__.

So here the super() of D leads to E (for __init__) which is not the same in the context of A.

Community
  • 1
  • 1
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • 1
    I think you missed the most important part there by ending your last sentence too soon: `super()` in `A` will delegate to `D` and the `super()` call in `D` will delegate to `B` whereas if you started with a `D` instance its `super()` call would go to `E`. – Duncan Feb 23 '17 at 11:12
  • 1
    Yes, your edit crossed with my comment. Much better though I still think it would be useful to show that for a direct D instance `super()` goes somewhere different. – Duncan Feb 23 '17 at 11:13
7
super().__init__(*args, **kwargs)   

Perceive you don't pass the "self" - it is inserted automatically.

super() was first designed in Python 2 to allow classes to be reused as mixins in a class hierarchy in a way that their immediate superclass may change:

Let's supose at some point in time your code is like:

class A: pass
class B(A): 
    def __init__(self, *args, **kwargs):
          ...
          # Fixed call to A
          A.__init__(self, *args, **kwargs)

class C(A):
    def __init__(self, *args, **kwargs):
          ...
          # Fixed call to A
          A.__init__(self, *args, **kwargs)

class D(C, B): 
    pass

At this point, correct OOP code should execute C.__init__ which should chain the call to B.__init__: but when the superclass name is hardcoded that does not happen - A's __init__ would always come next. And if you hardcode B.__init__ in C, you would prevent C from working without B, defeating the purpose of multiple inheritance.

When you use super() instead, Python's perform the method search for the next parent class looking on the class's __mro__ attribute (mro = method resolution order. __mro__ is a concrete attribute attached to each Python class). - So, if at some point in time class D above no longer inherits from B, the calls to super().__init__ in C will be automatically re-routed straight to A.

It is also worth noting that in Python 3 the parameterless form of super was introduced to ease its use - prior to that, one had to hardcode a reference to the own class and also insert self in the parameters. This form is one of the few exceptions in Python that is hardcoded in the compiler itself - it do change things internally on methods when super (or __class__) is seen inside a method body (namely, it creates a __class__ variable pointing to the class itself which the super call uses)

jsbueno
  • 99,910
  • 10
  • 151
  • 209