<< back

Herencia multiple y super() en python

Se que hay mucha polemica y muchos debates respeto a la herencia multiple, algunos estan a favor y otros como lo mas habitual, en contra. Seguramente todos tendran sus razones, todas dignas de ser tomadas en cuenta. La principal critica es que incorpora una complejidad al lenguaje y como tratar las ambiguedades, ademas de que cada lenguaje lo hace a su manera.

Pero en este caso estamos hablando de python, y a mi parecer, no lo hace tan mal, si no al reves, creo que resuelve este tema con bastante elegancia (casi igual que perl). En mi opinion creo que el tema de ambiguedades, diferencias de las clases y la complejidad que comporta, es dependiente del desarrollador, y no 100% de la forma en la que el lenguaje resuelve este tema.

Este articulo en realidad no viene a habalar de la herencia multiple en si, ni generar debates ni polemicas, si no que explicar por encima de como lo tiene implementado python y como aprovecharla mediante la funcion super().

¿Como funciona?

La herencia multiple en python se define como una lista de superclases, ordenada con una especie de algoritmo por orden de profundidad y ejecuta el primer metodo que encuentra. Para la curiosidad, esta lista se encuentra guardada en la propiedad __mro__ de la clase.

¿Si ejecuta el primer metodo que encuentra, que pasa con los metodos padres si es que existen?, pues es bastante facil, aqui es donde entra en juego super(). Su funcion principal es ejecutar el metodo padre.

Ejemplo 1

Para ver el funcionamiento basico de super(), puede observar el ejemplo1, el cual contiene 2 clases que imprimen su nombre, y ejecutan el metodo __init__ padre. En la lista de __mro__ como ya dije anteriormente, podemos observar el orden de prioridad/profundidad con las que se va ir ejecutando los metodos de las superclases.

# -*- coding: utf8 -*-

class A(object):
    def __init__(self):
        print "A"
        super(A, self).__init__()

class B(A):
    def __init__(self):
        print "B"
        super(B, self).__init__()

print "__mro__:", [x.__name__ for x in B.__mro__]
instance = B()

Y la salida de este ejemplo es:

~ > python2 example1.py
__mro__: ['B', 'A', 'object']
B
A

Ejemplo 2

En este ejemplo veremos un poco mas de complejidad, utilizando ya lo que tenemos del codigo anterior y añadimos 2 clases mas. Una que herede de A que la llamaremos C y otra que herede de B y C que la llamaremos D. Aqui es donde podremos ver en accion de como python maneja la herencia multiple. Como ya mencione en la introduccion, es bastante simple: primero ejecuta todas las superclases de primer nivel de profundidad y luego ejecuta el segundo nivel asi llegando hasta el final que es object.

Esto ultimo que acabo de contar funciona cuando tenemos una especie de rombo de dependencia, en otros casos pasa algo diferente que lo veremos en otros ejemplos.

Una cosa que tenemos que darnos cuenta, y si es que si muchas superclases de primer nivel heredan de una superclase del segundo nivel, solo se ejecutara una vez. Para verlo mas claro, mire ele ejemplo2.

# -*- coding: utf8 -*-

class A(object):
    def __init__(self):
        print "A"
        super(A, self).__init__()

class B(A):
    def __init__(self):
        print "B"
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C"
        super(C, self).__init__()

class D(C,B):
    def __init__(self):
        print "D"
        super(D, self).__init__()


print "__mro__:", [x.__name__ for x in D.__mro__]
instance = D()

Y la salida de este ejemplo es:

~ > python2 example2.py
__mro__: ['D', 'C', 'B', 'A', 'object']
D
C
B
A

Ejemplo 3

Una vez comprendidos los ejemplos anteriores, vamos a ver que pasa cuando a la clase D añadimos otra superclase, pero que esta vez esa superclase herede de otro objeto de segundo nivel, como podria ser el mismo object.

El funcionamiento, se puede deducir a simple vista, pero intentare explicarlo. El algoritmo que usa python, analiza el primer nivel y lo agrupa por la superclase de segundo nivel. Si nos encontramos en el caso del ejemplo 2, vemos que C y B heredan de una sola superclase que es A, entonces, el procedimiento es ejecutar C, B y luego A, pero en el caso de C y B heredaran de una superclase diferente el orden seria por profundidad. Es decir Que ejecutaria el __init__ de la superclase C y luego la superclase de la que hereda C, seguido de __init__ de B que a su vez ejecutaria el __init__ de la superclase de B.

Para ver con claridad, he creado una clase Z que deriba de object y la he añadido como superclase a D, a continuacion puede ver el codigo final y su ejecucion para que pueda ver con mas claridad el funcionamiento:

# -*- coding: utf8 -*-

class A(object):
    def __init__(self):
        print "A"
        super(A, self).__init__()

class B(A):
    def __init__(self):
        print "B"
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print "C"
        super(C, self).__init__()

class Z(object):
    def __init__(self):
        print "Z"
        super(Z, self).__init__()

class D(C,B,Z):
    def __init__(self):
        print "D"
        super(D, self).__init__()

print "__mro__:", [x.__name__ for x in D.__mro__]
instance = D()

Y la salida de este ejemplo es:

~ > python2 example3.py
__mro__: ['D', 'C', 'B', 'A', 'Z', 'object']
D
C
B
A
Z