Pregunta Método de resolución de orden (MRO) en las clases de nuevo estilo?


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 el orden de resolución clásico 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() huellas dactilares Base1, pero según mi comprensión del MRO con un nuevo estilo de clases, la salida debería haber sido Base3. La llamada Derived.__mro__ huellas dactilares:

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


75
2017-12-04 17:30


origen


Respuestas:


La diferencia crucial entre el orden de resolución para las clases antiguas frente a las nuevas se produce 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 de diamantes":

>>> 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 D.x, 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 anula (es decir, la anulación de C de 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.


147
2017-12-04 18:03



El orden de resolución de métodos de Python es en realidad más complejo que simplemente entender el patrón de diamantes. A De Verdad entiéndelo, eche un vistazo a Linealización 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 A B D C E F G?

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

18
2017-12-27 18:20



El resultado que obtienes es correcto. Intenta cambiar la clase base de Base3 a Base1 y comparar 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

Leer esta explicación para más información.


5
2017-12-04 17:46



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

Asi que instance.amethod()

  1. Comprueba Base2, no encuentra el método.
  2. Ve que Base2 ha heredado de Base1 y comprueba Base1. Base1 tiene un amethod, así se llama.

Esto se refleja en Derived.__mro__. Simplemente itere sobre Derived.__mro__ y pare cuando encuentre el método que se está buscando.


0
2017-12-04 17:53