Pregunta

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 el Right Thing (TM). 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, se imprime:

D init
B init
C init
A init

Sin embargo, si comenta la llamada a super en la función init de B, no se llama a A ni a la función init 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 get sobrecargado, pero ¿cómo el objeto devuelto por super en la definición init de D comunica la existencia de C al objeto devuelto por super en la definición init 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?

Editar: Jekke señaló acertadamente 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__ .

¿Fue útil?

Solución

He proporcionado un montón de enlaces a continuación, que responden a su pregunta con más detalle y con mayor precisión de la 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 tiene 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 ( say DC ).
  5. super busca métodos que coincidan (en su caso __init__ ) en las clases en la 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 asumo que sí. Ver que super de Python es ingenioso pero no se puede usar, enlace a continuación) Ese método luego causa una búsqueda en la clase del objeto 'MRO para el siguiente método, a la derecha de BC1 .
  7. Repita el lavado con enjuague hasta que se encuentren y se llamen todos los métodos.

Explicación para su ejemplo

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

    B .__ init__ encontrado y llamado


  1. B .__ init__ llama a super (B, self) .__ init __ () .

    isinstance (self, B) = > Falso
    isinstance (self, D) = > Verdadero

  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 se llama.

  3. Y así sucesivamente.

Una explicación de super
http://www.python.org/download/releases/2.2 .3 / descrintro / # cooperacion
Aspectos a tener en cuenta al utilizar super
http://fuhm.net/super-harmful/
Algoritmo MRO de Pythons:
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.

Otros consejos

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
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

También vale la pena visitar Python's Super es ingenioso, pero no puedes usarlo .

solo adivinando:

self en los cuatro métodos se refieren al mismo objeto, es decir, de la clase D . entonces, en B .__ init __ () , la llamada a super (B, self) conoce toda la ascendencia de diamantes de self y tiene para obtener el método desde 'después' 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)
<super: <class 'B'>, <D object>>

Esto resuelve la pregunta 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?

Es decir, en la definición de inicio 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 la hora va directamente 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 init de B, no se llama ni a la función init de A ni de C.

Para entenderlo, cambie el código de Jacob para imprimir la pila 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 B super (B, self) .__ init __ () en realidad está llamando a C .__ init __ () , ya que C no es una clase base de B .

D init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
B init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
C init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
A init
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
  File "/tmp/jacobs.py", line 31, in <module>
    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 la clase de base de B de __init__ '. En su lugar, es ' llamando a __init__ en la primera clase a la derecha de B que está presente en el self ' s __mro__ y que tiene ese atributo .

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

Para resumir:

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

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top