Understanding Python super() with __init__() methods

2,208,264

Solution 1

super() lets you avoid referring to the base class explicitly, which can be nice. But the main advantage comes with multiple inheritance, where all sorts of fun stuff can happen. See the standard docs on super if you haven't already.

Note that the syntax changed in Python 3.0: you can just say super().__init__() instead of super(ChildB, self).__init__() which IMO is quite a bit nicer. The standard docs also refer to a guide to using super() which is quite explanatory.

Solution 2

I'm trying to understand super()

The reason we use super is so that child classes that may be using cooperative multiple inheritance will call the correct next parent class function in the Method Resolution Order (MRO).

In Python 3, we can call it like this:

class ChildB(Base):
    def __init__(self):
        super().__init__()

In Python 2, we were required to call super like this with the defining class's name and self, but we'll avoid this from now on because it's redundant, slower (due to the name lookups), and more verbose (so update your Python if you haven't already!):

        super(ChildB, self).__init__()

Without super, you are limited in your ability to use multiple inheritance because you hard-wire the next parent's call:

        Base.__init__(self) # Avoid this.

I further explain below.

"What difference is there actually in this code?:"

class ChildA(Base):
    def __init__(self):
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        super().__init__()

The primary difference in this code is that in ChildB you get a layer of indirection in the __init__ with super, which uses the class in which it is defined to determine the next class's __init__ to look up in the MRO.

I illustrate this difference in an answer at the canonical question, How to use 'super' in Python?, which demonstrates dependency injection and cooperative multiple inheritance.

If Python didn't have super

Here's code that's actually closely equivalent to super (how it's implemented in C, minus some checking and fallback behavior, and translated to Python):

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        check_next = mro.index(ChildB) + 1 # next after *this* class.
        while check_next < len(mro):
            next_class = mro[check_next]
            if '__init__' in next_class.__dict__:
                next_class.__init__(self)
                break
            check_next += 1

Written a little more like native Python:

class ChildB(Base):
    def __init__(self):
        mro = type(self).mro()
        for next_class in mro[mro.index(ChildB) + 1:]: # slice to end
            if hasattr(next_class, '__init__'):
                next_class.__init__(self)
                break

If we didn't have the super object, we'd have to write this manual code everywhere (or recreate it!) to ensure that we call the proper next method in the Method Resolution Order!

How does super do this in Python 3 without being told explicitly which class and instance from the method it was called from?

It gets the calling stack frame, and finds the class (implicitly stored as a local free variable, __class__, making the calling function a closure over the class) and the first argument to that function, which should be the instance or class that informs it which Method Resolution Order (MRO) to use.

Since it requires that first argument for the MRO, using super with static methods is impossible as they do not have access to the MRO of the class from which they are called.

Criticisms of other answers:

super() lets you avoid referring to the base class explicitly, which can be nice. . But the main advantage comes with multiple inheritance, where all sorts of fun stuff can happen. See the standard docs on super if you haven't already.

It's rather hand-wavey and doesn't tell us much, but the point of super is not to avoid writing the parent class. The point is to ensure that the next method in line in the method resolution order (MRO) is called. This becomes important in multiple inheritance.

I'll explain here.

class Base(object):
    def __init__(self):
        print("Base init'ed")

class ChildA(Base):
    def __init__(self):
        print("ChildA init'ed")
        Base.__init__(self)

class ChildB(Base):
    def __init__(self):
        print("ChildB init'ed")
        super().__init__()

And let's create a dependency that we want to be called after the Child:

class UserDependency(Base):
    def __init__(self):
        print("UserDependency init'ed")
        super().__init__()

Now remember, ChildB uses super, ChildA does not:

class UserA(ChildA, UserDependency):
    def __init__(self):
        print("UserA init'ed")
        super().__init__()

class UserB(ChildB, UserDependency):
    def __init__(self):
        print("UserB init'ed")
        super().__init__()

And UserA does not call the UserDependency method:

>>> UserA()
UserA init'ed
ChildA init'ed
Base init'ed
<__main__.UserA object at 0x0000000003403BA8>

But UserB does in-fact call UserDependency because ChildB invokes super:

>>> UserB()
UserB init'ed
ChildB init'ed
UserDependency init'ed
Base init'ed
<__main__.UserB object at 0x0000000003403438>

Criticism for another answer

In no circumstance should you do the following, which another answer suggests, as you'll definitely get errors when you subclass ChildB:

super(self.__class__, self).__init__()  # DON'T DO THIS! EVER.

(That answer is not clever or particularly interesting, but in spite of direct criticism in the comments and over 17 downvotes, the answerer persisted in suggesting it until a kind editor fixed his problem.)

Explanation: Using self.__class__ as a substitute for the class name in super() will lead to recursion. super lets us look up the next parent in the MRO (see the first section of this answer) for child classes. If you tell super we're in the child instance's method, it will then lookup the next method in line (probably this one) resulting in recursion, probably causing a logical failure (in the answerer's example, it does) or a RuntimeError when the recursion depth is exceeded.

>>> class Polygon(object):
...     def __init__(self, id):
...         self.id = id
...
>>> class Rectangle(Polygon):
...     def __init__(self, id, width, height):
...         super(self.__class__, self).__init__(id)
...         self.shape = (width, height)
...
>>> class Square(Rectangle):
...     pass
...
>>> Square('a', 10, 10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
TypeError: __init__() missing 2 required positional arguments: 'width' and 'height'

Python 3's new super() calling method with no arguments fortunately allows us to sidestep this issue.

Solution 3

It's been noted that in Python 3.0+ you can use

super().__init__()

to make your call, which is concise and does not require you to reference the parent OR class names explicitly, which can be handy. I just want to add that for Python 2.7 or under, some people implement a name-insensitive behaviour by writing self.__class__ instead of the class name, i.e.

super(self.__class__, self).__init__()  # DON'T DO THIS!

HOWEVER, this breaks calls to super for any classes that inherit from your class, where self.__class__ could return a child class. For example:

class Polygon(object):
    def __init__(self, id):
        self.id = id

class Rectangle(Polygon):
    def __init__(self, id, width, height):
        super(self.__class__, self).__init__(id)
        self.shape = (width, height)

class Square(Rectangle):
    pass

Here I have a class Square, which is a sub-class of Rectangle. Say I don't want to write a separate constructor for Square because the constructor for Rectangle is good enough, but for whatever reason I want to implement a Square so I can reimplement some other method.

When I create a Square using mSquare = Square('a', 10,10), Python calls the constructor for Rectangle because I haven't given Square its own constructor. However, in the constructor for Rectangle, the call super(self.__class__,self) is going to return the superclass of mSquare, so it calls the constructor for Rectangle again. This is how the infinite loop happens, as was mentioned by @S_C. In this case, when I run super(...).__init__() I am calling the constructor for Rectangle but since I give it no arguments, I will get an error.

Solution 4

Super has no side effects

Base = ChildB

Base()

works as expected

Base = ChildA

Base()

gets into infinite recursion.

Solution 5

Just a heads up... with Python 2.7, and I believe ever since super() was introduced in version 2.2, you can only call super() if one of the parents inherit from a class that eventually inherits object (new-style classes).

Personally, as for python 2.7 code, I'm going to continue using BaseClassName.__init__(self, args) until I actually get the advantage of using super().

Share:
2,208,264
Mizipzor
Author by

Mizipzor

A crazy coder in a passionate hunt for greater wisdom. I take great interest in anything involving math and algorithms. Especially path finding, artificial life, cellular automata and emergent behavior.

Updated on July 08, 2022

Comments

  • Mizipzor
    Mizipzor almost 2 years

    Why is super() used?

    Is there a difference between using Base.__init__ and super().__init__?

    class Base(object):
        def __init__(self):
            print "Base created"
            
    class ChildA(Base):
        def __init__(self):
            Base.__init__(self)
            
    class ChildB(Base):
        def __init__(self):
            super(ChildB, self).__init__()
            
    ChildA() 
    ChildB()
    
    • Charlie Parker
      Charlie Parker over 2 years
      this is a very simple intro to classes worth going through: realpython.com/python-super/…. It's easier to digest than the answers given that are for most of us I assume too detailed in the implementation of python. It also has examples to make it concrete.
  • Mizipzor
    Mizipzor about 15 years
    I see. Could you elaborate a little as to why its nicer to use super() with multiple inheritance? To me, the base.__init__(self) is shorter (cleaner). If I had two baseclasses, it would be two of those lines, or two super() lines. Or did I misunderstand what you meant by "nicer"?
  • Devin Jeanpierre
    Devin Jeanpierre about 15 years
    Actually, it would be one super() line. When you have multiple inheritance, the MRO is still flat. So the first super().__init__ call calls the next class's init, which then calls the next, and so on. You should really check out some docs on it.
  • James Brady
    James Brady about 15 years
    The child class MRO contains object too - a class's MRO is visible in the mro class variable.
  • James Brady
    James Brady about 15 years
    Also note that classic classes (pre 2.2) don't support super - you have to explicitly refer to base classes.
  • Devin Jeanpierre
    Devin Jeanpierre about 15 years
    "The child class MRO contains object too - a class's MRO is visible in the mro class variable." That is a big oops. Whoops.
  • andilabs
    andilabs almost 11 years
    very good point. IF you don't clearly mention: class Base(object): then you will get error like that: "TypeError: must be type, not classobj"
  • glglgl
    glglgl about 10 years
    What this answer suggests, super(self.__class__, self).__init__() does not work if you subclass again without providing a new __init__. Then you have an infinite recursion.
  • nispio
    nispio over 8 years
    This. The meaning of super(ChildB, self) changes depending on the MRO of the object referred to by self, which cannot be known until runtime. In other words, the author of ChildB has no way of knowing what super() will resolve to in all cases unless they can guarantee that ChildB will never be subclassed.
  • nispio
    nispio over 8 years
    To say that "the MRO has exactly one item" assumes that ChildB will never be subclassed. You don't actually know until runtime what the MRO will be when the call to super() happens.
  • Two-Bit Alchemist
    Two-Bit Alchemist about 8 years
    @andi I got that error the other day and I eventually just gave up trying to figure it out. I was just messing around on iPython. What a freaking nightmare of a bad error message if that was actually code I had to debug!
  • Veky
    Veky almost 8 years
    This answer is ridiculous. If you're going to abuse super this way, you might as well just hardcode the base class name. It is less wrong than this. The whole point of first argument of super is that it's not necessarily the type of self. Please read "super considered super" by rhettinger (or watch some of his videos).
  • Ryan Hiebert
    Ryan Hiebert over 7 years
    The shortcut demonstrated here for Python 2 has pitfalls that have been mentioned already. Don't use this, or your code will break in ways you can't predict. This "handy shortcut" breaks super, but you may not realize it until you've sunk a whole lot of time into debugging. Use Python 3 if super is too verbose.
  • Yohan Obadia
    Yohan Obadia almost 7 years
    I'll still need to work my head around this super() function, however, this answer is clearly the best in terms of depth and details. I also appreciate greatly the criticisms inside the answer. It also help to better understand the concept by identifying pitfalls in other answers. Thank you !
  • hunch
    hunch almost 7 years
    @Aaron Hall, thanks for so detailed information. I think there should be one more option available(atleast) to mentors to call some answer as inappropriate or incomplete if they do not provide correct sufficient information.
  • Russia Must Remove Putin
    Russia Must Remove Putin over 6 years
    The statement, "Super has no side effects," doesn't make sense in this context. Super simply guarantees we call the correct next class's method in the method resolution order, whereas the other way hard-codes the next method to be called, which makes cooperative multiple inheritance more difficult.
  • Tino
    Tino over 6 years
    Edited the answer. Sorry if that edit changes the meaning 180 degrees, but now this answer should make some sense.
  • AnjoMan
    AnjoMan over 6 years
    @Tino I don't really agree with your edits. It doesn't make sense to say both that one cannot do something and that one shouldn't do it - it is possible to do what I describe in my post, and for the reasons I lay out it is a bad idea.
  • Russia Must Remove Putin
    Russia Must Remove Putin over 6 years
    What makes no sense is to tell someone they can do something that is trivially demonstrated as incorrect. You can alias echo to python. Nobody would ever suggest it!
  • Stevoisiak
    Stevoisiak about 6 years
    Can you provide an example of super() being used with arguments?
  • Xarses
    Xarses about 6 years
    Thanks, this was super helpful. The criticism of the poor/improper usage was very illustrative of why, and how to use super
  • rooni
    rooni almost 6 years
    Can you please explain super(ChildB, self).__init__() this , what does ChildB and self have to do with the super
  • SeasonalShot
    SeasonalShot over 5 years
    This answer has a point that's all. I wouldn't have thought of doing this since it is a bad design, now that we know, it an be avoided.
  • Omnik
    Omnik over 5 years
    @rimiro The syntax of super() is super([type [, object]]) This will return the superclass of type. So in this case the superclass of ChildB will be returned. If the second argument is omitted, the super object returned is unbound. If the second argument is an object, then isinstance(object, type) must be true.
  • Mike - SMT
    Mike - SMT over 5 years
    I have been using tk.Tk.__init__(self) over super().__init__() as I didn't fully understand what super was but this post has been very enlightening. I guess in the case of Tkinter classes tk.Tk.__init__(self) and super().__init__() are the same thing but it looks like you are saying we should avoid doing something like Base.__init__(self) so I may be switching to super() even though I am still trying to grasp its complexity.
  • physincubus
    physincubus about 5 years
    this answer is especially comprehensive, and really filled in gaps in my knowledge. hats off to you sir.
  • eric
    eric about 5 years
    If you are here and still confused, please read the answer by Aaron Hall you will leave this page much happier: stackoverflow.com/a/27134600/1886357
  • Charlie Parker
    Charlie Parker almost 5 years
    can you actually explain what the code does? I don't want to click to 1 million more places to find the answer to this.
  • facehugger
    facehugger about 4 years
    Your second "native Python" example behaves differently than the first example (and as super-based solution). hasattr(next_class, '__init__') used may implicitly indicate the presence of an unnecessary method. For example, imagine canonical diamond hierarchy class A(): ...; class B(A): ...; class C(A): ...; class D(B, C): ... where A and С have their own __init__ methods and D has __init__ such as in the described example. Then calling D().__init__() will cause A.__init__ to call instead of C.__init__
  • facehugger
    facehugger about 4 years
    Maybe if '__init__' in next_class.__dict__: is better
  • Russia Must Remove Putin
    Russia Must Remove Putin about 4 years
    @facehugger it's a translation of a translation - it's not supposed to behave exactly the same, it's supposed to retain the semantics while being as simple as possible.
  • MarkHu
    MarkHu over 3 years
    This answer is fragmentary (code examples only makes sense as a continuation of code from the answer.)