Saltar al contenido

¿Cómo funciona super() de Python con herencia múltiple?

Posteriormente a consultar expertos en el tema, programadores de diversas ramas y profesores hemos dado con la respuesta a la cuestión y la dejamos plasmada en esta publicación.

Solución:

Esto se detalla con una cantidad razonable de detalles por parte del propio Guido en su publicación de blog Method Resolution Order (incluidos dos intentos anteriores).

En tu ejemplo, Third() llamará First.__init__. Python busca cada attribute en los padres de la clase como se enumeran de izquierda a derecha. En este caso, estamos buscando __init__. Entonces, si defines

class Third(First, Second):
    ...

Python comenzará mirando Firsty si First no tiene el attributeentonces mirará Second.

Esta situación se vuelve más compleja cuando la herencia comienza a cruzarse (por ejemplo, si First heredado de Second). Lea el enlace anterior para obtener más detalles, pero, en pocas palabras, Python intentará mantener el orden en que aparece cada clase en la lista de herencia, comenzando con la clase secundaria en sí.

Entonces, por ejemplo, si tuvieras:

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"

el MRO sería [Fourth, Second, Third, First].

Por cierto: si Python no puede encontrar un orden de resolución de método coherente, generará una excepción, en lugar de recurrir al comportamiento que podría sorprender al usuario.

Editado para agregar un ejemplo de un MRO ambiguo:

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"

Deberían Third‘s MRO ser [First, Second] o [Second, First]? No hay una expectativa obvia, y Python generará un error:

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

Editar: Veo a varias personas argumentando que los ejemplos anteriores carecen super() llamadas, déjame explicarte: el objetivo de los ejemplos es mostrar cómo se construye el MRO. Ellos son no destinado a imprimir “primeronsegundotercero” o lo que sea. Puede, y debe, por supuesto, jugar con el ejemplo, agregar super() llamadas, vea lo que sucede y obtenga una comprensión más profunda del modelo de herencia de Python. Pero mi objetivo aquí es mantenerlo simple y mostrar cómo se construye el MRO. Y se construye como expliqué:

>>> Fourth.__mro__
(,
 , ,
 ,
 )

Su código y las otras respuestas tienen errores. les falta el super() llamadas en las dos primeras clases que se requieren para que funcione la subclase cooperativa.

Aquí hay una versión fija del código:

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

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

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

los super() call encuentra el siguiente método en el MRO en cada paso, razón por la cual First y Second también deben tenerlo; de lo contrario, la ejecución se detiene al final de Second.__init__().

Esto es lo que obtengo:

>>> Third()
second
first
third

Quería elaborar un poco la respuesta sin vida porque cuando comencé a leer sobre cómo usar super() en una jerarquía de herencia múltiple en Python, no lo entendí de inmediato.

Lo que tienes que entender es que super(MyClass, self).__init__() proporciona el próximo__init__ método según el algoritmo Method Resolution Ordering (MRO) utilizado en el contexto de la jerarquía de herencia completa.

Esta última parte es crucial para entender. Consideremos el ejemplo de nuevo:

#!/usr/bin/env python2

class First(object):
  def __init__(self):
    print "First(): entering"
    super(First, self).__init__()
    print "First(): exiting"

class Second(object):
  def __init__(self):
    print "Second(): entering"
    super(Second, self).__init__()
    print "Second(): exiting"

class Third(First, Second):
  def __init__(self):
    print "Third(): entering"
    super(Third, self).__init__()
    print "Third(): exiting"

De acuerdo con este artículo sobre Orden de Resolución de Método de Guido van Rossum, la orden para resolver __init__ se calcula (antes de Python 2.3) usando un “recorrido de izquierda a derecha primero en profundidad”:

Third --> First --> object --> Second --> object

Después de eliminar todos los duplicados, excepto el último, obtenemos:

Third --> First --> Second --> object

Entonces, sigamos lo que sucede cuando creamos una instancia de la Third clase, por ejemplo x = Third().

  1. Según MRO Third.__init__ ejecuta
    • huellas dactilares Third(): entering
    • luego super(Third, self).__init__() se ejecuta y MRO regresa First.__init__ Lo que es llamado.
  2. First.__init__ ejecuta
    • huellas dactilares First(): entering
    • luego super(First, self).__init__() se ejecuta y MRO regresa Second.__init__ Lo que es llamado.
  3. Second.__init__ ejecuta
    • huellas dactilares Second(): entering
    • luego super(Second, self).__init__() se ejecuta y MRO regresa object.__init__ Lo que es llamado.
  4. object.__init__ ejecuta (no hay declaraciones de impresión en el código allí)
  5. la ejecución se remonta a Second.__init__ que luego imprime Second(): exiting
  6. la ejecución se remonta a First.__init__ que luego imprime First(): exiting
  7. la ejecución se remonta a Third.__init__ que luego imprime Third(): exiting

Esto detalla por qué instanciar Third() da como resultado:

Third(): entering
First(): entering
Second(): entering
Second(): exiting
First(): exiting
Third(): exiting

El algoritmo MRO se ha mejorado desde Python 2.3 en adelante para que funcione bien en casos complejos, pero supongo que usar el “recorrido de izquierda a derecha primero en profundidad” + “eliminar duplicados, esperar el último” todavía funciona en la mayoría de los casos (por favor comenten si este no es el caso). ¡Asegúrate de leer la publicación de blog de Guido!

Sección de Reseñas y Valoraciones

Más adelante puedes encontrar las referencias de otros gestores de proyectos, tú igualmente puedes mostrar el tuyo si lo deseas.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *