Categories
class inheritance oop python super

Understanding Python super() with __init__() methods [duplicate]

3030

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()

2

  • 11

    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.

    Nov 5, 2021 at 18:07

  • I still don’t get it. I want to define a class Event(tuple) which creates tuples (timestamp, description) and where the timestamp should default to the current time. Thus, something like e = Event(description="stored the current time") should give an instance of the subclass Event of the tuple (1653520485,"stored..."). But in __init__() I cannot modify self as I would do for a subclass of dict. So I thought I could use super().__init__ to set the components of the tuple self. Can I ?

    – Max

    May 25 at 23:16

2198

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.

3

  • 11

    @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.

    – Omnik

    Aug 30, 2018 at 15:48

  • 9

    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

    – eric

    Feb 18, 2019 at 23:53

  • 9

    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.

    Jun 4, 2019 at 23:00

1106

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.

6

  • 53

    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 !

    May 29, 2017 at 15:09

  • 4

    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.

    Nov 8, 2018 at 15:55


  • 3

    this answer is especially comprehensive, and really filled in gaps in my knowledge. hats off to you sir.

    Feb 10, 2019 at 20:26

  • 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__

    Feb 10, 2020 at 21:33

  • Maybe if '__init__' in next_class.__dict__: is better

    Feb 10, 2020 at 21:34

274

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.

5

  • 44

    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.

    – glglgl

    Mar 31, 2014 at 7:21


  • 25

    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).

    – Veky

    Jul 29, 2016 at 12:54


  • 6

    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.

    Jan 13, 2017 at 16:17

  • @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.

    – AnjoMan

    Nov 22, 2017 at 2:16

  • 5

    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!

    Dec 2, 2017 at 23:18