¿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:
super
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 escribeD
miB.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 cuentaB
solo las clases base.