Understanding Python Super() With _Init_() Methods

Understanding Python super() with __init__() methods

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.

What is the use of super(ClassName,self)._init_()

How does it differ from self.x=x?

super() is only useful if you subclass:

class Foo(object):
def __init__(self, x):
self.x = x

class Bar(Foo):
def __init__(self, x):
super(Bar, self).__init__(x)
self.initial_status = False

is better than setting self.x = x in Bar's __init__.

The difference is that Bar doesn't need to care about the implementation of Foo.

If you choose to change Foo in a way which sets self.x = 2 * x, then you won't have to change Bar as well (which might even sit in a difference file - failure to see this is almost guaranteed).

In your example, there is no point to use super() as you don't subclass.

When calling super().__init__ how can I tell if the super init is object.__init__ in python3?

The only way that I was able to get this so work was to build a new temporary class
containing the remaining super classes, then checking the __init__ method of that class.

def super_init(self, super_instance, *args, **kwargs):
classes_in_order = self.__class__.__mro__
for i, cls in enumerate(classes_in_order):
if cls == super_instance.__thisclass__:
remainder_cls = type('_unused', classes_in_order[i+1:], {})
super_init_is_object_init = remainder_cls.__init__ == object.__init__
if not super_init_is_object_init:
super_instance.__init__(*args, **kwargs)

These attempts didn't work:

  • checking super().__init__ == object.__init__ did not work
  • checking super().__init__ is object.__init__ did not work
  • checking super(super().__thisclass__, self) vs object.__init__ did not work
  • introspecting the function signatures with inspect.signature did not work

Should __init__() call the parent class's __init__()?

In Python, calling the super-class' __init__ is optional. If you call it, it is then also optional whether to use the super identifier, or whether to explicitly name the super class:

object.__init__(self)

In case of object, calling the super method is not strictly necessary, since the super method is empty. Same for __del__.

On the other hand, for __new__, you should indeed call the super method, and use its return as the newly-created object - unless you explicitly want to return something different.

How does Python's super() work with multiple inheritance?

This is detailed with a reasonable amount of detail by Guido himself in his blog post Method Resolution Order (including two earlier attempts).

In your example, Third() will call First.__init__. Python looks for each attribute in the class's parents as they are listed left to right. In this case, we are looking for __init__. So, if you define

class Third(First, Second):
...

Python will start by looking at First, and, if First doesn't have the attribute, then it will look at Second.

This situation becomes more complex when inheritance starts crossing paths (for example if First inherited from Second). Read the link above for more details, but, in a nutshell, Python will try to maintain the order in which each class appears on the inheritance list, starting with the child class itself.

So, for instance, if you had:

class First(object):
def __init__(self):
print "first"

class Second(First):
def __init__(self):
print "second"

class Third(First):
def __init__(self):
print "third"

class Fourth(Second, Third):
def __init__(self):
super(Fourth, self).__init__()
print "that's it"

the MRO would be [Fourth, Second, Third, First].

By the way: if Python cannot find a coherent method resolution order, it'll raise an exception, instead of falling back to behavior which might surprise the user.

Example of an ambiguous MRO:

class First(object):
def __init__(self):
print "first"

class Second(First):
def __init__(self):
print "second"

class Third(First, Second):
def __init__(self):
print "third"

Should Third's MRO be [First, Second] or [Second, First]? There's no obvious expectation, and Python will raise an error:

TypeError: Error when calling the metaclass bases
Cannot create a consistent method resolution order (MRO) for bases Second, First

Why do the examples above lack super() calls? The point of the examples is to show how the MRO is constructed. They are not intended to print "first\nsecond\third" or whatever. You can – and should, of course, play around with the example, add super() calls, see what happens, and gain a deeper understanding of Python's inheritance model. But my goal here is to keep it simple and show how the MRO is built. And it is built as I explained:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
<class '__main__.Second'>, <class '__main__.Third'>,
<class '__main__.First'>,
<type 'object'>)

What is the purpose of _init_()?

I am not an expert on this but AFAIK the __init__ method is a built-in method of every class or metaclass, that is called exactly in the class instantiation.

Why or what for would you use the __init__ method?

Well, you can use it for many things, but the main purpose is to pass arguments into the class instance when it is instantiated. Actually when you do class(*args, **kwargs) these get passed along to __init__, where you might either make use of them or not.

For example:

class Vehicle:

name = 'vehicle'
price = None
canMove = False
position = 0

def __init__(self, price, name=None, canMove=False):
self.name = name if name else self.name
self.price = price

def move(self, distance):
if self.canMove:
self.position += distance
return 'The vehicle moved {0} meters'.format(distance)
return 'The vehicle cannot move'


class Car(Vehicle):
name = 'car'
consumption = 100 # (litres per meter)
fuel = 0 # (litres )

def __init__(self, fuel, consumption=None, *args, **kwargs):
self.fuel = fuel
self.consumption = consumption if consumption else self.consumption
super().__init__(*args, **kwargs)

def move(self, distance):
if self.canMove and self.hasFuel():
available_d = self.fuel / self.consumption
if distance <= available_d:
self.fuel -= self.consumption*distance
self.position += distance
return 'The vehicle moved {0} meters and has {1} litres left.'.format(distance, self.fuel)
return 'The vehicle cannot move since it does not have fuel enough'
return 'The vehicle cannot move since it does not have any fuel'


def hasFuel(self):
return True if self.fuel > 0 else False

def giveFuel(self, litres):
self.fuel += litres

If you take some time to read the code, you will see how there are necessary tasks that need to be run in the __init__ method, such as variable assignment, doing checks or running other processes.

Also when you inherit from another class the behaviour gets a bit more complicated. As you see I have to call the mother class with the super().__init__() call in order to run the rest of the tasks that were being done in it.

__init__ in use

So let's play around with this piece of code I have just created.

You could create your vehicle just like this:

myVehicle = Vehicle()

However that would raise an error, since the __init__ method is requiring to pass one compulsory argument, price. All the rest are optional fields, but that is obligatory for the instantiation to happen. Therefore you could try again with this:

myVehicle = Vehicle(10000)

You can access the value you have just passed doing:

myVehicle.price

You can also pass the rest of the elements, but they are not obligatory:

myVehicle2 = Vehicle(10000, name='myVehicle2', canMove=True)

Bear in mind that we could also assign them after the instantiation to our first vehicle:

myVehicle.name = 'myVehicle'

So now we have two different vehicles, but one can move and the other can't. We can see this if we run:

myVehicle.move(100)

Whereas myVehicle2.move(100) won't raise the error. If you change the property of the first vehicle afterwards it will work, regardless of the value you passed to __init__ initially.

It works similar for our Car class. If we do Car():

>>> Car()
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: __init__() missing 1 required positional argument: 'fuel'

Even if we do Car(fuel=140):

>>> Car(fuel=140)
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 10, in __init__
TypeError: __init__() missing 1 required positional argument: 'price'

All these arguments are defined in the __init__ section, and the price tag is required because it inherits form the parent class.

Look at how the values work correctly when we try to move our car:

>>> myCar = Car(price=10000, fuel=150, consumption=10)
>>> myCar.move(10)
'The vehicle cannot move since it does not have any fuel' # we actually have not enabled its canMove property
>>> myCar.canMove = True
>>> myCar.move(10)
'The vehicle moved 10 meters and has 50 litres left.'
>>> myCar.move(1)
'The vehicle moved 1 meters and has 40 litres left.'
>>> myCar.move(30)
'The vehicle cannot move since it does not have fuel enough'

Problems with _init_()

I think there is a missunderstanding __init__ with double underscores is the right usage not _init_ not with single underscore on both sides.

You wrote like this;

def _init_(self, app, mixer, sound_file):

But it must be this;

def __init__(self, app, mixer, sound_file):


Related Topics



Leave a reply



Submit