pythoninheritanceconstructorsuperself

Why does __init__ requires an explicit self as an argument when calling it as base.__init__()?


I know of two ways to call a superclass's constructor from a derived class: super().__init__() and base.__init__(self). Why does the second require me to explicitly supply self as an argument?

Here's a snippet that demonstrates the difference:

class base:
    def __init__(self,/):
        print("initializing")

class der(base):
    def __init__(self):
        super().__init__()  #no error
        base.__init__(self) #no error
        base.__init__() #error, self required

obj = der()

The third constructor call throws this error:

File "demo.py", line 11, in <module>
    obj = der()
          ^^^^^
  File "demo.py", line 9, in __init__
    base.__init__() #error, should be: base.__init__(self)
    ^^^^^^^^^^^^^^^
TypeError: base.__init__() missing 1 required positional argument: 'self'

I expect base.__init__() to work since super().__init__() doesn't require an explicit self.


Solution

  • TL;DR: super() is an instance. der is a class.

    To undersand this properly, you have to understand how instance methods in Python work. When you write an instance method, you have to include self as the first parameter like this:

    class Foo:
        def bar(self): ...
    

    This is because when you call an instance method, Python automatically supplies the instance itself as the method's first argument under the hood. These two lines will do the same thing:

    x.bar()
    Foo.bar(x)
    

    The only difference is that the first line calls bar from an instance and the second calls bar from a class.

    In your example, base is a class (the equivalent to Foo) instead of an instance (the equivalent of x). Thus, if you want to call the base constructor, you need to manually supply self - Python won't do it for you.

    The super version doesn't need that because super isn't the class base - it's a proxy for the instance self that pretends it's of type der instead of type base. That means super() is still an instance, not a class, so super().__init__() will automatically supply self as a parameter to base.__init__ and you don't need to do it manually.