-I was reading an article (https://rhettinger.wordpress.com/2011/05/26/super-considered-super/)
about the function super()
for multiple inheritance in Python. It caught my attention because, literally in the first line, it says that if you are not amazed with how it works, it means that you do not know it well. The point is that you mention that the main advantage of using this closest parent reference class is that, unlike referencing the parent class directly, it does an undirected lookup, which is supposed to be better. First I have to say that I do not speak English 100% which is partly to blame for this question. On the other hand, the question would finally be: why is it better to reference a parent class through super()
, that is...
class persona:
def __init__(self, nombre, apellido, edad, sexo):
self.nombre = nombre
self.apellido = apellido
self.sexo = sexo
self.edad = edad
class empleado(persona):
def __init__(self, nombre, apellido, edad, sexo, salario):
super().__init__(nombre, apellido,edad, sexo)
self.salario = salario
-Instead of referencing the parent class through the reference itself...
class persona:
def __init__(self, nombre, apellido, edad, sexo):
self.nombre = nombre
self.apellido = apellido
self.sexo = sexo
self.edad = edad
class empleado(persona):
def __init__(self, nombre, apellido, edad, sexo, salario):
persona.__init__(self,nombre, apellido,edad, sexo)
self.salario = salario
Using
super()
has two advantages:You avoid explicitly putting the name of the parent class. This gives you more flexibility in making the code for those functions independent of the class they inherit from.
In other words, following your example, if you decide to change your mind and make
Empleado
, instead of inheritingPersona
from another class, let's sayTrabajador
, the code you put as the second example would stop working correctly, since it would still be calling the constructor ofPersona
instead of the ofTrabajador
. For it to work correctly you would have to changePersona
to worker in all the methods of the classEmpleado
(it does not have to appear only in__init__()
, it is common that other methods are also extended in derived classes).Using
super()
you don't need to touch anything in the classEmpleado
, except for the declaration of the class itselfclass
to indicate that it now inherits fromTrabajador
.The resolution of
super()
which class to use happens at run time and not at compile time. This allows flexible mechanisms especially in the face of multiple inheritance.To clarify point 2, which is the most complex, imagine that we have your example classes implemented:
But now we want to create a new type of "Person" that is an artificial intelligence, which, in addition to not having an age or gender (so it will ignore what it receives in those two parameters), will have an attribute
is_a_bot
.Well, now comes the fun of using
super()
. We can create a new class that is aEmpleadoAI
that inherits from bothEmpleado
andInteligenciaArtificial
, by simply writing:The class body is empty (
pass
), so its method__init__()
is not actually written, but is inherited fromEmpleado
. But whenEmpleado
internally you dosuper().__init__()
, now your "super" will no longer bePersona
, butInteligenciaArtificial
, because we have put this class "in the middle" betweenEmpleado
andInteligenciaArtificial
.The inheritance structure is of the "diamond" type:
But Python has that graph "serialized" in what is called the MRO ( Method Resolution Order ) that depends on the order of declaration of the classes in the case of multiple inheritance. Because it
EmpleadoAI
declares to inherit first fromEmpleado
and afterInteligenciaArtificial
, the MRO will have these two classes in that order (and after both it will havePersona
).You can check the MRO of a class:
That is, when trying to invoke a method of
EmpleadoAI
, it will look first inEmpleadoAI
, and if it's not there,Empleado
then inInteligenciaArtificial
, and finally inPersona
(and if not in the classobject
they all inherit from if they don't declare another).Being
super()
evaluated at runtime, this allows you to know if itEmpleado.__init__()
was called when instantiating an objectEmpleado
(in which case itsuper()
would resolve toPersona
), or if it is being called via anothersuper()
fromEmpleadoAI
(in which case it would resolve toInteligenciaArtificial
).An example of object creation and the output it produces will clarify the behavior:
We see the order in which the constructors are called and how the
InteligenciaArtificial
one of has "sneaked" betweenEmpleado
andPersona
. We can also see that the attributes finally initialized in this special employee are those of an AI:First of all, let me tell you that, as a style rule , in python the names of the classes are capitalized :
Persona
,Empleado
.The operation of inheritance is based on the mechanism that python has to search for inherited attributes between the parent classes, related to the so-called Descriptor Protocol ^1 . Thanks to this mechanism, multiple inheritance works , metaclasses , properties and abstract classes , to name a few language features.
Between put:
Y
they differ in who is responsible for looking up the method
__init__
among the ancestors . WithPersona.__init__(self,...)
you are forcing the class toPersona
use the instanceself
and initialize it. Withsuper().__init__(...)
you are telling the instanceself
to be initialized as an instance of the parent class (which doesn't have to bePersona
, as we'll see later).To see it better, we are going to add more classes, and we will stop
Empleado
directly invokingPersona
, withoutsuper()
:The first observation is that in
Viajante
a single line with is enoughsuper()
for the instance initialization to work correctly, as wellEmpleado
asConductor
. How is this achieved?The trick is called the MRO Algorithm . Without going into details, this algorithm applies certain criteria by which the entire class hierarchy is linearly ordered. In practice, we turn a multiple hierarchy into a single hierarchy , making it easier to search.
Let's look at an example:
The result of the MRO algorithm is stored in the class attribute
__mro__
. If we look inViajante.__mro__
we see the tuple:(Viajante, Empleado, Conductor, Persona, object)
. This will be the search order that the instance will usepedro
, starting withViajante
and ending withobject
.Using
super()
we are propagating the initialization up the class hierarchy.super()
gets the type attribute__mro__
of the instance and checks it against the class from which it is invoked. This way it knows which is the next class in the sequence to pass the call to.Thus, the
super()
enViajante
sees that the next class in the chain isEmpleado
. InEmpleado
,super()
you would see that the following isConductor
, from there toPersona
, and from there toobject
.But in the class
Empleado
we had left a direct call toPersona.__init__
, without usingsuper()
, so the classConductor
that corresponded to MRO would be skipped.When you programmed
Empleado
it possibly works the same usingsuper()
or making the direct call. But if some other programmer wanted to use your code, they might find that multiple inheritance doesn't work as it should.I have tried to summarize enough. It is not necessary to know the inner workings of the MRO algorithm to work with multiple inheritance. However, you may run into the error that the MRO algorithm is not capable of serializing a certain class hierarchy. Unfortunately, these errors are not easy to solve, even if you try to understand the algorithm.