Frage Method Resolution Order (MRO) in neuartigen Klassen?


Im Buch Python in Kürze (2. Auflage) Es gibt ein Beispiel, das verwendet
alte Stilklassen, um zu demonstrieren, wie Methoden in klassischer Auflösungsreihenfolge aufgelöst werden
Wie ist es anders bei der neuen Ordnung?

Ich habe das gleiche Beispiel versucht, indem ich das Beispiel im neuen Stil umgeschrieben habe, aber das Ergebnis unterscheidet sich nicht von dem, was mit alten Stilklassen erreicht wurde. Die Python-Version, die ich verwende, um das Beispiel auszuführen, ist 2.5.2. Unten ist das Beispiel:

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__  

Der Anruf instance.amethod() Drucke Base1, aber nach meinem Verständnis der MRO mit neuen Klassenstil sollte die Ausgabe gewesen sein Base3. Der Anruf Derived.__mro__ Drucke:

(<class '__main__.Derived'>, <class '__main__.Base2'>, <class '__main__.Base1'>, <class '__main__.Base3'>, <type 'object'>)

Ich bin mir nicht sicher, ob mein Verständnis von MRO mit neuen Stilklassen falsch ist oder dass ich einen dummen Fehler mache, den ich nicht erkennen kann. Bitte helfen Sie mir, MRO besser zu verstehen.


75
2017-12-04 17:30


Ursprung


Antworten:


Der entscheidende Unterschied zwischen der Auflösungsreihenfolge für Legacy-vs-New-Style-Klassen liegt vor, wenn die gleiche Vorfahrklasse mehr als einmal im "naiven" Tiefenanflug-Ansatz auftritt - z. B. einen "Diamond-Vererbung" -Fall berücksichtigen:

>>> class A: x = 'a'
... 
>>> class B(A): pass
... 
>>> class C(A): x = 'c'
... 
>>> class D(B, C): pass
... 
>>> D.x
'a'

Hier ist Legacy-Stil, die Reihenfolge der Auflösung ist D - B - A - C - A: so, wenn D.x, A ist die erste Basis in Auflösung Reihenfolge, um es zu lösen, wodurch die Definition in C versteckt. Während:

>>> class A(object): x = 'a'
... 
>>> class B(A): pass
... 
>>> class C(A): x = 'c'
... 
>>> class D(B, C): pass
... 
>>> D.x
'c'
>>> 

hier, neuer Stil, ist die Reihenfolge:

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

mit A gezwungen, in Auflösungsreihenfolge nur einmal und nach allen seinen Unterklassen zu kommen, so dass Überschreibungen (d. h. C's Überschreibung von Mitglied x) arbeiten tatsächlich vernünftig.

Es ist einer der Gründe, warum Old-Style-Klassen vermieden werden sollten: Multiple Vererbung mit "diamond-like" Mustern funktioniert bei ihnen nicht sinnvoll, während es mit New-Style funktioniert.


147
2017-12-04 18:03



Pythons Methodenauflösungsreihenfolge ist tatsächlich komplexer als nur das Rautenmuster zu verstehen. Zu Ja wirklich verstehe es, schau es dir an C3-Linearisierung. Ich habe festgestellt, dass es wirklich hilfreich ist, print-Anweisungen zu verwenden, wenn Methoden zur Verfolgung der Bestellung erweitert werden. Was wäre zum Beispiel die Ausgabe dieses Musters? (Anmerkung: das 'X' soll zwei kreuzende Kanten sein, kein Knoten und ^ bedeutet Methoden, die super () aufrufen)

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

Hast du A B D C E F G bekommen?

x = A()
x.m()

Nach vielen Versuchen mit einem Fehler, kam ich mit einer informellen graphentheoretischen Interpretation der C3-Linearisierung wie folgt auf: (Jemand bitte lassen Sie mich wissen, wenn das falsch ist.)

Betrachten Sie dieses Beispiel:

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



Das Ergebnis ist korrekt. Versuchen Sie, die Basisklasse von zu ändern Base3 zu Base1 und vergleichen Sie mit derselben Hierarchie für klassische Klassen:

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

Jetzt gibt es aus:

Base3
Base1

Lesen diese Erklärung für mehr Informationen.


5
2017-12-04 17:46



Sie sehen dieses Verhalten, weil die Methodenauflösung in der Tiefe liegt, nicht in der Breite. Dervieds Erbe sieht so aus

         Base2 -> Base1
        /
Derived - Base3

Damit instance.amethod()

  1. Überprüft Base2, findet kein Verfahren.
  2. Sieht, dass Base2 von Base1 geerbt hat und Base1 überprüft. Base1 hat a amethod, so wird es genannt.

Dies spiegelt sich in Derived.__mro__. Einfach über iterieren Derived.__mro__ und hör auf, wenn du die gesuchte Methode findest.


0
2017-12-04 17:53