¿Cómo el “super” de Python hace lo correcto?

Estoy ejecutando Python 2.5, por lo que es posible que esta pregunta no se aplique a Python 3. Cuando creas una jerarquía de clases de diamante con herencia múltiple y creas un objeto de la clase más derivada, Python hace lo correcto. Llama al constructor para la clase más derivada, luego a sus clases primarias como se enumeran de izquierda a derecha, luego al abuelo. Estoy familiarizado con el MRO de Python; esa no es mi pregunta Tengo curiosidad por saber cómo el objeto devuelto de super realmente logra comunicarse con las llamadas de super en el orden correcto. Considere este código de ejemplo:

#!/usr/bin/python class A(object): def __init__(self): print "A init" class B(A): def __init__(self): print "B init" super(B, self).__init__() class C(A): def __init__(self): print "C init" super(C, self).__init__() class D(B, C): def __init__(self): print "D init" super(D, self).__init__() x = D() 

El código hace lo intuitivo, imprime:

 D init B init C init A init 

Sin embargo, si comenta la llamada a super en la función de inicio de B, no se llama a A ni a la función de inicio de C. Esto significa que la llamada de B a super es de alguna manera consciente de la existencia de C en la jerarquía de clases general. Sé que super devuelve un objeto proxy con un operador de sobrecarga sobrecargado, pero ¿cómo el objeto devuelto por super en la definición de inicio de D comunica la existencia de C al objeto devuelto por super en la definición de inicio de B? ¿La información que subsiguientes llamadas de uso super está almacenada en el propio objeto? Si es así, ¿por qué no es super en lugar de self.super?

Edición: Jekke señaló con razón que no es self.super porque super es un atributo de la clase, no una instancia de la clase. Conceptualmente, esto tiene sentido, pero en la práctica, ¡super tampoco es un atributo de la clase! Puede probar esto en el intérprete haciendo dos clases A y B, donde B hereda de A y llamando a dir(B) . No tiene atributos super o __super__ .

He proporcionado un montón de enlaces a continuación, que responden a su pregunta con más detalle y más precisamente de lo que puedo esperar. Sin embargo, también le daré una respuesta a su pregunta con mis propias palabras, para ahorrarle algo de tiempo. Lo pondré en puntos –

  1. super es una función incorporada, no un atributo.
  2. Cada tipo (clase) en Python tiene un atributo __mro__ , que almacena el orden de resolución del método de esa instancia en particular.
  3. Cada llamada a super es de la forma super (tipo [, object-or-type]). Supongamos que el segundo atributo es un objeto por el momento.
  4. En el punto de inicio de las súper llamadas, el objeto es del tipo de la clase Derivada (por ejemplo, DC ).
  5. super busca métodos que coincidan (en su caso __init__ ) en las clases en el MRO, después de la clase especificada como el primer argumento (en este caso, las clases después de DC).
  6. Cuando se encuentra el método de coincidencia (por ejemplo, en la clase BC1 ), se llama.
    (Este método debería usar super, así que supongo que sí. Ver que super de Python es ingenioso pero no se puede usar – enlace a continuación). Ese método entonces causa una búsqueda en la clase del objeto ‘MRO para el siguiente método, a la derecha de BC1 .
  7. Enjuague, repita hasta que todos los métodos sean encontrados y llamados.

Explicación para tu ejemplo

  MRO: D,B,C,A,object 
  1. Se llama super(D, self).__init__() . isinstance (self, D) => True
  2. Busque el siguiente método en el MRO en las clases a la derecha de D.

    B.__init__ encontrado y llamado


  1. B.__init__ llama super(B, self).__init__() .

    isinstance (self, B) => False
    isinstance (self, D) => True

  2. Por lo tanto, el MRO es el mismo, pero la búsqueda continúa a la derecha de B, es decir, C, A, el objeto se busca uno por uno. El siguiente __init__ encontrado es llamado.

  3. Y así sucesivamente y así sucesivamente.

Una explicacion de super
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
Cosas a tener en cuenta al usar super
http://fuhm.net/super-harmful/
Algoritmo de Pythons MRO:
http://www.python.org/download/releases/2.3/mro/
documentos de super:
http://docs.python.org/library/functions.html
La parte inferior de esta página tiene una buena sección sobre super:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

Espero que esto ayude a aclararlo.

Cambie su código a esto y creo que explicará las cosas (presumiblemente, super está mirando dónde, por ejemplo, B está en el __mro__ ?):

 class A(object): def __init__(self): print "A init" print self.__class__.__mro__ class B(A): def __init__(self): print "B init" print self.__class__.__mro__ super(B, self).__init__() class C(A): def __init__(self): print "C init" print self.__class__.__mro__ super(C, self).__init__() class D(B, C): def __init__(self): print "D init" print self.__class__.__mro__ super(D, self).__init__() x = D() 

Si lo ejecutas verás:

 D init (, , , , ) B init (, , , , ) C init (, , , , ) A init (, , , , ) 

También vale la pena echarle un vistazo a Python’s Super es ingenioso, pero no puedes usarlo .

solo adivinando:

self en los cuatro métodos se refiere al mismo objeto, es decir, de la clase D así, en B.__init__() , la llamada a super(B,self) conoce toda la ascendencia de diamantes del self y tiene que buscar el método desde ‘after’ B En este caso, es la clase C

super() conoce la jerarquía de clases completa . Esto es lo que sucede dentro del inicio de B:

 >>> super(B, self) , > 

Esto resuelve la cuestión central,

¿Cómo el objeto devuelto por super en la definición de inicio de D comunica la existencia de C al objeto devuelto por super en la definición de inicio de B?

A saber, en la definición inicial de B, self es una instancia de D , y por lo tanto comunica la existencia de C Por ejemplo, C se puede encontrar en type(self).__mro__ .

La respuesta de Jacob muestra cómo entender el problema, mientras que Batbrat muestra los detalles y el de RRHH va directo al grano.

Una cosa que no cubren (al menos no explícitamente) de su pregunta es este punto:

Sin embargo, si comenta la llamada a super en la función de inicio de B, no se llama a A ni a la función de inicio de C.

Para entender eso, cambie el código de Jacob para imprimir la stack en el inicio de A, como se muestra a continuación:

 import traceback class A(object): def __init__(self): print "A init" print self.__class__.__mro__ traceback.print_stack() class B(A): def __init__(self): print "B init" print self.__class__.__mro__ super(B, self).__init__() class C(A): def __init__(self): print "C init" print self.__class__.__mro__ super(C, self).__init__() class D(B, C): def __init__(self): print "D init" print self.__class__.__mro__ super(D, self).__init__() x = D() 

Es un poco sorprendente ver que la línea de super(B, self).__init__() realidad está llamando a C.__init__() , ya que C no es una clase básica de B

 D init (, , , , ) B init (, , , , ) C init (, , , , ) A init (, , , , ) File "/tmp/jacobs.py", line 31, in  x = D() File "/tmp/jacobs.py", line 29, in __init__ super(D, self).__init__() File "/tmp/jacobs.py", line 17, in __init__ super(B, self).__init__() File "/tmp/jacobs.py", line 23, in __init__ super(C, self).__init__() File "/tmp/jacobs.py", line 11, in __init__ traceback.print_stack() 

Esto sucede porque super (B, self) no está ‘ llamando a la versión de __init__ de __init__ de __init__ de __init__ ‘. En cambio, es ‘ llamar a __init__ en la primera clase a la derecha de B que está presente en el __mro__ y que tiene ese atributo .

Por lo tanto, si comenta la llamada a super en la función init de B , la stack de métodos se detendrá en B.__init__ , y nunca alcanzará C o A

Para resumir:

  • Independientemente de qué clase se refiera a ella, self es siempre una referencia a la instancia, y sus __mro__ y __class__ permanecen constantes
  • super () encuentra el método mirando a las clases que están a la derecha del actual en __mro__ . Como el __mro__ permanece constante, lo que sucede es que se busca como una lista, no como un árbol o una gráfica.

En ese último punto, tenga en cuenta que el nombre completo del algoritmo de MRO es la linealización de la superclase C3 . Es decir, aplana esa estructura en una lista. Cuando suceden las diferentes llamadas super() , están iterando efectivamente esa lista.