Why aren't superclass __init__ methods automatically invoked?

Viewed 88790

Why did the Python designers decide that subclasses' __init__() methods don't automatically call the __init__() methods of their superclasses, as in some other languages? Is the Pythonic and recommended idiom really like the following?

class Superclass(object):
    def __init__(self):
        print 'Do something'

class Subclass(Superclass):
    def __init__(self):
        super(Subclass, self).__init__()
        print 'Do something else'
11 Answers

To avoid confusion it is useful to know that you can invoke the base_class __init__() method if the child_class does not have an __init__() class.

Example:

class parent:
  def __init__(self, a=1, b=0):
    self.a = a
    self.b = b

class child(parent):
  def me(self):
    pass

p = child(5, 4)
q = child(7)
z= child()

print p.a # prints 5
print q.b # prints 0
print z.a # prints 1

In fact the MRO in python will look for __init__() in the parent class when can not find it in the children class. You need to invoke the parent class constructor directly if you have already an __init__() method in the children class.

For example the following code will return an error: class parent: def init(self, a=1, b=0): self.a = a self.b = b

    class child(parent):
      def __init__(self):
        pass
      def me(self):
        pass

    p = child(5, 4) # Error: constructor gets one argument 3 is provided.
    q = child(7)  # Error: constructor gets one argument 2 is provided.

    z= child()
    print z.a # Error: No attribute named as a can be found.

As Sergey Orshanskiy pointed out in the comments, it is also convenient to write a decorator to inherit the __init__ method.

You can write a decorator to inherit the __init__ method, and even perhaps automatically search for subclasses and decorate them. – Sergey Orshanskiy Jun 9 '15 at 23:17

Part 1/3: The implementation

Note: actually this is only useful if you want to call both the base and the derived class's __init__ since __init__ is inherited automatically. See the previous answers for this question.

def default_init(func):
    def wrapper(self, *args, **kwargs) -> None:
        super(type(self), self).__init__(*args, **kwargs)
    return wrapper

class base():
    def __init__(self, n: int) -> None:
        print(f'Base: {n}')

class child(base):
    @default_init
    def __init__(self, n: int) -> None:
        pass
        
child(42)

Outputs:

Base: 42

Part 2/3: A warning

Warning: this doesn't work if base itself called super(type(self), self).

def default_init(func):
    def wrapper(self, *args, **kwargs) -> None:
        '''Warning: recursive calls.'''
        super(type(self), self).__init__(*args, **kwargs)
    return wrapper

class base():
    def __init__(self, n: int) -> None:
        print(f'Base: {n}')

class child(base):
    @default_init
    def __init__(self, n: int) -> None:
        pass
        
class child2(child):
    @default_init
    def __init__(self, n: int) -> None:
        pass
        
child2(42)

RecursionError: maximum recursion depth exceeded while calling a Python object.

Part 3/3: Why not just use plain super()?

But why not just use the safe plain super()? Because it doesn't work since the new rebinded __init__ is from outside the class, and super(type(self), self) is required.

def default_init(func):
    def wrapper(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
    return wrapper

class base():
    def __init__(self, n: int) -> None:
        print(f'Base: {n}')

class child(base):
    @default_init
    def __init__(self, n: int) -> None:
        pass
        
child(42)

Errors:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-9-6f580b3839cd> in <module>
     13         pass
     14 
---> 15 child(42)

<ipython-input-9-6f580b3839cd> in wrapper(self, *args, **kwargs)
      1 def default_init(func):
      2     def wrapper(self, *args, **kwargs) -> None:
----> 3         super().__init__(*args, **kwargs)
      4     return wrapper
      5 

RuntimeError: super(): __class__ cell not found

Background - We CAN AUTO init a parent AND child class!

A lot of answers here and say "This is not the python way, use super().__init__() from the subclass". The question is not asking for the pythonic way, it's comparing to the expected behavior from other languages to python's obviously different one.

The MRO document is pretty and colorful but it's really a TLDR situation and still doesn't quite answer the question, as is often the case in these types of comparisons - "Do it the Python way, because.".

Inherited objects can be overloaded by later declarations in subclasses, a pattern building on @keyvanrm's (https://stackoverflow.com/a/46943772/1112676) answer solves the case where I want to AUTOMATICALLY init a parent class as part of calling a class without explicitly calling super().__init__() in every child class.

In my case where a new team member might be asked to use a boilerplate module template (for making extensions to our application without touching the core application source) which we want to make as bare and easy to adopt without them needing to know or understand the underlying machinery - to only need to know of and use what is provided by the application's base interface which is well documented.

For those who will say "Explicit is better than implicit." I generally agree, however, when coming from many other popular languages inherited automatic initialization is the expected behavior and it is very useful if it can be leveraged for projects where some work on a core application and others work on extending it.

This technique can even pass args/keyword args for init which means pretty much any object can be pushed to the parent and used by the parent class or its relatives.

Example:


class Parent:
    def __init__(self, *args, **kwargs):
        self.somevar = "test"
        self.anothervar = "anothertest"

        #important part, call the init surrogate pass through args:
        self._init(*args, **kwargs)

    #important part, a placeholder init surrogate:
    def _init(self, *args, **kwargs):
        print("Parent class _init; ", self, args, kwargs)

    def some_base_method(self):
        print("some base method in Parent")
        self.a_new_dict={}


class Child1(Parent):
    # when omitted, the parent class's __init__() is run
    #def __init__(self):
    #    pass

    #overloading the parent class's  _init() surrogate
    def _init(self, *args, **kwargs):
        print(f"Child1 class _init() overload; ",self, args, kwargs)

        self.a_var_set_from_child = "This is a new var!"


class Child2(Parent):
    def __init__(self, onevar, twovar, akeyword):
        print(f"Child2 class __init__() overload; ", self)

        #call some_base_method from parent
        self.some_base_method()

        #the parent's base method set a_new_dict
        print(self.a_new_dict)


class Child3(Parent):
    pass


print("\nRunning Parent()")
Parent()
Parent("a string", "something else", akeyword="a kwarg")

print("\nRunning Child1(), keep Parent.__init__(), overload surrogate Parent._init()")
Child1()
Child1("a string", "something else", akeyword="a kwarg")

print("\nRunning Child2(), overload Parent.__init__()")
#Child2() # __init__() requires arguments
Child2("a string", "something else", akeyword="a kwarg")

print("\nRunning Child3(), empty class, inherits everything")
Child3().some_base_method()

Output:

Running Parent()
Parent class _init;  <__main__.Parent object at 0x7f84a721fdc0> () {}
Parent class _init;  <__main__.Parent object at 0x7f84a721fdc0> ('a string', 'something else') {'akeyword': 'a kwarg'}

Running Child1(), keep Parent.__init__(), overload surrogate Parent._init()
Child1 class _init() overload;  <__main__.Child1 object at 0x7f84a721fdc0> () {}
Child1 class _init() overload;  <__main__.Child1 object at 0x7f84a721fdc0> ('a string', 'something else') {'akeyword': 'a kwarg'}

Running Child2(), overload Parent.__init__()
Child2 class __init__() overload;  <__main__.Child2 object at 0x7f84a721fdc0>
some base method in Parent
{}

Running Child3(), empty class, inherits everything, access things set by other children
Parent class _init;  <__main__.Child3 object at 0x7f84a721fdc0> () {}
some base method in Parent

As one can see, the overloaded definition(s) take the place of those declared in Parent class but can still be called BY the Parent class thereby allowing one to emulate the classical implicit inheritance initialization behavior Parent and Child classes both initialize without needing to explicitly invoke the Parent's init() from the Child class.

Personally, I call the surrogate _init() method main() because it makes sense to me when switching between C++ and Python for example since it is a function that will be automatically run for any subclass of Parent (the last declared definition of main(), that is).

Related