python - Método de resolución de orden(MRO) en las clases de nuevo estilo?
method-resolution-order (4)
En el libro Python in a Nutshell (2nd Edition) hay un ejemplo que usa
clases de estilo antiguo para demostrar cómo se resuelven los métodos en orden de resolución clásica y
¿Cómo es diferente con el nuevo orden?
Intenté el mismo ejemplo reescribiendo el ejemplo en un nuevo estilo, pero el resultado no es diferente de lo que se obtuvo con las clases de estilo anteriores. La versión de Python que estoy usando para ejecutar el ejemplo es 2.5.2. A continuación está el ejemplo:
class Base1(object):
def amethod(self): print "Base1"
class Base2(Base1):
pass
class Base3(object):
def amethod(self): print "Base3"
class Derived(Base2,Base3):
pass
instance = Derived()
instance.amethod()
print Derived.__mro__
La llamada instance.amethod()
imprime Base1
, pero según mi comprensión de la MRO con un nuevo estilo de clases, la salida debería haber sido Base3
. La llamada Derived.__mro__
prints:
(<class ''__main__.Derived''>, <class ''__main__.Base2''>, <class ''__main__.Base1''>, <class ''__main__.Base3''>, <type ''object''>)
No estoy seguro de si mi comprensión de MRO con las nuevas clases de estilo es incorrecta o de que estoy cometiendo un error tonto que no puedo detectar. Por favor, ayúdenme a comprender mejor MRO.
El orden de resolución de métodos de Python es en realidad más complejo que simplemente entender el patrón de diamantes. Para entenderlo realmente , eche un vistazo a la linealización de C3 . Descubrí que realmente ayuda utilizar las declaraciones de impresión al extender los métodos para rastrear el pedido. Por ejemplo, ¿cuál crees que sería el resultado de este patrón? (Nota: la ''X'' se supone que es dos bordes que cruzan, no un nodo y ^ significa métodos que llaman super ())
class G():
def m(self):
print("G")
class F(G):
def m(self):
print("F")
super().m()
class E(G):
def m(self):
print("E")
super().m()
class D(G):
def m(self):
print("D")
super().m()
class C(E):
def m(self):
print("C")
super().m()
class B(D, E, F):
def m(self):
print("B")
super().m()
class A(B, C):
def m(self):
print("A")
super().m()
# A^
# / /
# B^ C^
# /| X
# D^ E^ F^
# / | /
# G
¿Recibiste ABDCEFG?
x = A()
x.m()
Después de un gran error de prueba, se me ocurrió una interpretación informal de la teoría de grafos de la linealización de C3 de la siguiente manera: (Alguien, por favor, avíseme si esto está mal).
Considera este ejemplo:
class I(G):
def m(self):
print("I")
super().m()
class H():
def m(self):
print("H")
class G(H):
def m(self):
print("G")
super().m()
class F(H):
def m(self):
print("F")
super().m()
class E(H):
def m(self):
print("E")
super().m()
class D(F):
def m(self):
print("D")
super().m()
class C(E, F, G):
def m(self):
print("C")
super().m()
class B():
def m(self):
print("B")
super().m()
class A(B, C, D):
def m(self):
print("A")
super().m()
# Algorithm:
# 1. Build an inheritance graph such that the children point at the parents (you''ll have to imagine the arrows are there) and
# keeping the correct left to right order. (I''ve marked methods that call super with ^)
# A^
# / | /
# / | /
# B^ C^ D^ I^
# / | / / /
# / | X /
# / |/ / /
# E^ F^ G^
# / | /
# / | /
# H
# (In this example, A is a child of B, so imagine an edge going FROM A TO B)
# 2. Remove all classes that aren''t eventually inherited by A
# A^
# / | /
# / | /
# B^ C^ D^
# / | / /
# / | X
# / |/ /
# E^ F^ G^
# / | /
# / | /
# H
# 3. For each level of the graph from bottom to top
# For each node in the level from right to left
# Remove all of the edges coming into the node except for the right-most one
# Remove all of the edges going out of the node except for the left-most one
# Level {H}
#
# A^
# / | /
# / | /
# B^ C^ D^
# / | / /
# / | X
# / |/ /
# E^ F^ G^
# |
# |
# H
# Level {G F E}
#
# A^
# / | /
# / | /
# B^ C^ D^
# | / /
# | X
# | | /
# E^F^ G^
# |
# |
# H
# Level {D C B}
#
# A^
# /| /
# / | /
# B^ C^ D^
# | |
# | |
# | |
# E^ F^ G^
# |
# |
# H
# Level {A}
#
# A^
# |
# |
# B^ C^ D^
# | |
# | |
# | |
# E^ F^ G^
# |
# |
# H
# The resolution order can now be determined by reading from top to bottom, left to right. A B C E D F G H
x = A()
x.m()
El resultado que obtienes es correcto. Intente cambiar la clase base de Base3
a Base1
y compare con la misma jerarquía para las clases clásicas:
class Base1(object):
def amethod(self): print "Base1"
class Base2(Base1):
pass
class Base3(Base1):
def amethod(self): print "Base3"
class Derived(Base2,Base3):
pass
instance = Derived()
instance.amethod()
class Base1:
def amethod(self): print "Base1"
class Base2(Base1):
pass
class Base3(Base1):
def amethod(self): print "Base3"
class Derived(Base2,Base3):
pass
instance = Derived()
instance.amethod()
Ahora produce:
Base3
Base1
Lee esta explicación para más información.
Está viendo ese comportamiento porque la resolución del método es primero en profundidad, no en amplitud. La herencia de Dervied parece
Base2 -> Base1
/
Derived - Base3
Así instance.amethod()
- Comprueba Base2, no encuentra el método.
- Ve que Base2 ha heredado de Base1 y comprueba Base1. Base1 tiene un
amethod
, por lo que se llama.
Esto se refleja en Derived.__mro__
. Simplemente itere sobre Derived.__mro__
y deténgalo cuando encuentre el método que se está buscando.
La diferencia crucial entre el orden de resolución para las clases heredadas frente a las nuevas aparece cuando la misma clase antecesora aparece más de una vez en el enfoque "ingenuo", primero en profundidad; por ejemplo, considere un caso de "herencia diamantina":
>>> class A: x = ''a''
...
>>> class B(A): pass
...
>>> class C(A): x = ''c''
...
>>> class D(B, C): pass
...
>>> D.x
''a''
aquí, estilo legado, el orden de resolución es D - B - A - C - A: así que cuando se busca Dx, A es la primera base en resolución para resolverlo, ocultando así la definición en C. Mientras:
>>> class A(object): x = ''a''
...
>>> class B(A): pass
...
>>> class C(A): x = ''c''
...
>>> class D(B, C): pass
...
>>> D.x
''c''
>>>
aquí, nuevo estilo, el orden es:
>>> D.__mro__
(<class ''__main__.D''>, <class ''__main__.B''>, <class ''__main__.C''>,
<class ''__main__.A''>, <type ''object''>)
con A
forzado a entrar en orden de resolución solo una vez y después de todas sus subclases, por lo que las anulaciones (es decir, la anulación de C del miembro x
) realmente funcionan con sensatez.
Esta es una de las razones por las que se deben evitar las clases antiguas: la herencia múltiple con patrones "tipo diamante" simplemente no funciona de manera sensata con ellos, mientras que sí lo hace con el nuevo estilo.