2009-03-03 18 views
46

Estoy ejecutando Python 2.5, por lo que esta pregunta puede no aplicarse a Python 3. Cuando crea una jerarquía de clases diamantinas utilizando herencia múltiple y crea un objeto de la clase derivada, Python hace Right Thing (TM). Llama al constructor para la clase derivada, luego a sus clases principales enumeradas de izquierda a derecha, y luego a los abuelos. Estoy familiarizado con Python's MRO; esa no es mi pregunta. Tengo curiosidad de cómo el objeto devuelto de súper en realidad se las arregla para comunicarse con las llamadas de super en las clases principales en el orden correcto. Considere este ejemplo de código:¿Cómo hace el "super" de Python lo correcto?

#!/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 no lo intuitivo, imprime:

D init 
B init 
C init 
A init 

Sin embargo, si en comentario la llamada a super en la función init del B, ni A ni la función init del C se llama. Esto significa que la llamada de B a super es de algún modo 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 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 las llamadas subsiguientes de superusuario almacenan en el objeto mismo? Si es así, ¿por qué no es super en su lugar self.super?

Editar: 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 el súper 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 llama al dir(B). No tiene atributos super o __super__.

+0

Super no es self.super porque super es una propiedad de la clase, no la instancia. (Realmente no entiendo el resto de la pregunta.) –

+0

Irrelevante, tal vez; pero varias personas me han aconsejado que ya no utilicen Python 2.5, ya que Python 3 presenta tantas características nuevas/corrige tantos errores. – adam

+0

Editar a '¿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'? Creo que eso es lo que estás preguntando, podría tener más sentido :) –

Respuesta

14

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

  1. super es una función integrada, 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 [, objeto-o-tipo]). Supongamos que el segundo atributo es un objeto por el momento.
  4. En el punto de partida de las llamadas súper, el objeto es del tipo de la clase derivada (diga DC).
  5. super busca los métodos que coinciden (en su caso __init__) en las clases en el MRO, después de la clase especificada como el primer argumento (en este caso 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 debe usar súper, entonces supongo que sí - Ver el super de Python es ingenioso pero no se puede usar - enlace a continuación) Ese método provoca una búsqueda en la clase del objeto 'MRO para el siguiente método, para el derecho de BC1.
  7. Enjuague la repetición de lavado hasta encontrar y llamar a todos los métodos.

Explicación por su ejemplo

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

    B.__init__ encontrado y llamado

  3. B.__init__ llamadas super(B, self).__init__().

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

  4. Así, el MRO es el mismo, pero la búsqueda continúa a la derecha de B es decir, C, A , objeto se buscan uno por uno. Se llama al siguiente __init__ encontrado.

  5. Y así sucesivamente.

Una explicación de Súper
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation
cosas a tener en cuenta cuando se utiliza súper
http://fuhm.net/super-harmful/
pitones MRO Algoritmo:
http://www.python.org/download/releases/2.3/mro/
docs es super:
http://docs.python.org/library/functions.html
La parte inferior de esta página tiene una sección agradable en súper:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

Espero que esto ayude a aclararlo.

+0

en su explicación, no entiendo el 3er punto "isinstance (self, B) => False", ¿por qué continúa la búsqueda? – Alcott

6

sólo una suposición:

self en todos 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 el ancestro completo de diamantes de self y tiene que buscar el método de 'después' B. en este caso, es la clase C.

+0

' self' debería referirse a la instancia de la clase contenedora, y no a otra clase más adelante, ¿no? –

+1

no, si llama a 'self.method()' sería la implementación más específica, no importa cuán arriba esté. esa es la esencia del polimorfismo – Javier

34

cambiar el código para esto y creo que va a explicar las cosas (presumiblemente super se buscan en donde, por ejemplo, es B 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 ejecuta verá :

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 echarle un vistazo a Python's Super is nifty, but you can't use it.

+2

tal como lo supuse: yo soy la clase D todo el tiempo. – Javier

+0

Interesante, hubiera supuesto que si __mro__ apareciera al hacer dir (clase), pero no es así. Pero si haces dir (clase .__ clase__) ¡entonces está visible! ¿Alguna idea de por qué la discrepancia? class .__ mro__ y class .__ class __.__ mro__ both work –

+3

La razón es que __mro__ está definido por la metaclase, no por la clase, por lo que no aparece en el directorio (ver http://mail.python.org/pipermail/ python-dev/2008-March/077604.html). –

3

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

>>> super(B, self) 
<super: <class 'B'>, <D object>> 

Esto resuelve la cuestión central,

¿cómo el objeto devuelto por súper en D's definición init comunicar la existencia de C para el objeto devuelto por súper en La definición de inicio de B?

A saber, en la definición init 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__.

1

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

Una cosa que no cubren (al menos no explícitamente) de su pregunta es la siguiente cuestión:

Sin embargo, si en comentario la llamada a super en la función init del B, ni A ni la función init del C se llama.

a entender que, cambio de código de Jacob a imprimir la pila en init A, de la siguiente manera:

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() 

que es un poco sorprendente ver que B 'línea de s super(B, self).__init__() es en realidad llamando C.__init__(), como 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 no es super (B, self) 'llamar a la clase base de la versión B del __init__'. En cambio, es 'llamando al __init__ en la primera clase a la derecha de B que está presente en self' s __mro__ y que tiene dicho atributo.

lo tanto, si comentario la llamada a super en la función init del B, la pila método se detendrá en B.__init__, y nunca llegará a C o A.

En resumen:

  • Independientemente de la clase se refería a ella, self es siempre una referencia a la instancia, y su __mro__ y __class__ permanecen constantes
  • super() encuentra el método que mira al 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 este último punto, tenga en cuenta que el nombre completo del algoritmo de la MRO es C3 superclase linealización. Es decir, aplana esa estructura en una lista. Cuando se producen las diferentes llamadas super(), están efectuando una iteración efectiva de esa lista.

Cuestiones relacionadas