¿Cómo funciona realmente el super () de Python, en el caso general?

Hay muchos recursos excelentes ensuper(), incluyendoesta gran publicación de blog que aparece mucho, así como muchas preguntas sobre Stack Overflow. Sin embargo, siento que todos no explican cómo funciona en el caso más general (con gráficos de herencia arbitrarios), así como lo que está sucediendo debajo del capó.

Considere este ejemplo básico de herencia de diamantes:

class A(object):
    def foo(self):
        print 'A foo'

class B(A):
    def foo(self):
        print 'B foo before'
        super(B, self).foo()
        print 'B foo after'

class C(A):
    def foo(self):
        print 'C foo before'
        super(C, self).foo()
        print 'C foo after'

class D(B, C):
    def foo(self):
        print 'D foo before'
        super(D, self).foo()
        print 'D foo after'

Si lees las reglas de Python para el orden de resolución de métodos de fuentes comoesta o mira elpágina de wikipedia para la linealización C3, verá que el MRO debe ser(D, B, C, A, object). Por supuesto, esto es confirmado porD.__mro__:

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

Y

d = D()
d.foo()

huellas dactilares

D foo before
B foo before
C foo before
A foo
C foo after
B foo after
D foo after

que coincide con el MRO. Sin embargo, considere eso arribasuper(B, self).foo() enB en realidad llamaC.foo, Mientras enb = B(); b.foo() simplemente iría directamente aA.foo. Claramente usandosuper(B, self).foo() no es simplemente un atajo paraA.foo(self) como a veces se enseña.

super() obviamente es consciente de las llamadas anteriores y el MRO general que la cadena está tratando de seguir. Puedo ver dos maneras en que esto podría lograrse. El primero es hacer algo como pasar elsuper objetar a sí mismo como elself argumento para el siguiente método en la cadena, que actuaría como el objeto original pero también contiene esta información. Sin embargo, esto también parece que rompería muchas cosas (super(D, d) is d es falso) y al experimentar un poco puedo ver que este no es el caso.

La otra opción es tener algún tipo de contexto global que almacene el MRO y la posición actual en él. Me imagino el algoritmo parasuper va algo así como:

¿Existe actualmente un contexto en el que estamos trabajando? Si no, cree uno que contenga una cola. Obtenga el MRO para el argumento de clase, empuje todos los elementos excepto el primero en la cola.Haga estallar el siguiente elemento de la cola MRO del contexto actual, úselo como la clase actual al construir elsuper ejemplo.Cuando se accede a un método desdesuper Por ejemplo, búscalo en la clase actual y llámalo usando el mismo contexto.

Sin embargo, esto no tiene en cuenta las cosas raras, como usar una clase base diferente como primer argumento para una llamada asuper, o incluso llamando a un método diferente en él. Me gustaría saber el algoritmo general para esto. Además, si este contexto existe en algún lugar, ¿puedo inspeccionarlo? ¿Puedo jugar con eso? Una idea terrible, por supuesto, pero Python generalmente espera que seas un adulto maduro, incluso si no lo eres.

Esto también introduce muchas consideraciones de diseño. Si yo escribieraB pensando solo en su relación conA, luego alguien más escribeC y una tercera persona escribeDmiB.foo() método tiene que llamarsuper de manera compatible conC.foo() ¡aunque no existía en el momento en que lo escribí! Si quiero que mi clase sea fácilmente extensible, tendré que dar cuenta de esto, pero no estoy seguro de si es más complicado que simplemente asegurarme de que todas las versiones defoo tener firmas idénticas También está la cuestión de cuándo poner código antes o después de la llamada asuper, incluso si no hace ninguna diferencia teniendo en cuentaBsolo las clases base.

Respuestas a la pregunta(1)

Su respuesta a la pregunta