python - desde - django tutorial español pdf




¿Cómo hace el "súper" de Python lo correcto? (4)

Estoy ejecutando Python 2.5, por lo que esta pregunta puede no aplicarse a Python 3. Cuando crea una jerarquía de clase de diamante usando herencia múltiple y crea un objeto de la clase derivada, Python hace lo correcto (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 el MRO de Python; 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 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 init de B, no se llama ni a la función init de A ni de C. 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 toda 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 a dir(B) . No tiene atributos super o __super__ .


Cambia tu 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 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 comprobar que el Super de Python es ingenioso, pero no puedes usarlo .


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 espero. 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 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 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 (por ejemplo, DC ).
  5. súper 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, 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 super, así que 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 el MRO de la clase del objeto para el siguiente método, a la derecha de BC1 .
  7. Enjuague la repetición de lavado hasta encontrar y llamar a todos los métodos.

Explicación para su ejemplo

 MRO: D,B,C,A,object  
  1. se llama a 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

  3. B.__init__ llama a super(B, self).__init__() .

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

  4. Por lo tanto, el MRO es el mismo, pero la búsqueda continúa a la derecha de B, es decir, C, A, se buscan objetos uno por uno. El siguiente __init__ encontrado se llama.

  5. Y así sucesivamente y así sucesivamente.

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

Espero que esto ayude a aclararlo.


super() conoce la jerarquía de clase completa . Esto es lo que sucede dentro de init 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?

A saber, en la definición de inicio de B, el 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 hrr 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 init de B, no se llama ni a la función init de A ni de C.

Para entender eso, 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 de super(B, self).__init__() 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 __init__ base B de __init__ '. En cambio, está ' invocando __init__ en la primera clase a la derecha de B que está presente en el __mro__ y que tiene dicho atributo .

Entonces, 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 a qué clase se refiera, el self siempre es 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 el __mro__ . Como __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 MRO es la linealización de la clase superpuesta C3 . Es decir, aplana esa estructura en una lista. Cuando ocurren las diferentes llamadas a super() , están efectuando una iteración efectiva de esa lista.





python-2.5