Pytanie Metoda Resolution Order (MRO) w klasach nowego stylu?


W książce Python in a Nutshell (2nd Edition) istnieje przykład, który wykorzystuje
klas w starym stylu, aby zademonstrować, w jaki sposób metody są rozwiązywane w klasycznej kolejności rozwiązywania i
jak to się ma z nowym zamówieniem.

Próbowałem tego samego przykładu, przepisując przykład w nowym stylu, ale wynik nie różni się od tego, co uzyskano w przypadku klas starego stylu. Wersja Pythona, której używam do uruchomienia przykładu, jest 2.5.2. Poniżej znajduje się przykład:

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__  

Telefon instance.amethod() drukuje Base1, ale zgodnie z moim rozumieniem MRO z nowym stylem klas powinno być wyjście Base3. Telefon Derived.__mro__ drukuje:

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

Nie jestem pewien, czy moje rozumienie MRO z nowymi klasami stylu jest nieprawidłowe lub że robię głupi błąd, którego nie jestem w stanie wykryć. Pomóż mi lepiej zrozumieć MRO.


76
2017-12-04 17:30


pochodzenie




Odpowiedzi:


Zasadnicza różnica między porządkiem rozdzielczości dla klas starszych w stosunku do klas w nowym stylu ma miejsce, gdy ta sama klasa przodków występuje więcej niż raz w "naiwności", podejście z głębokim początkiem - np. Rozważmy przypadek "dziedziczenia diamentu":

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

tutaj, w stylu starszym, kolejność rozdzielczości to D - B - A - C - A: więc podczas wyszukiwania D.x, A jest pierwszą podstawą w kolejności rozdzielczości, aby ją rozwiązać, ukrywając w ten sposób definicję w C. Podczas gdy:

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

tutaj, w nowym stylu, kolejność jest następująca:

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

z A zmuszony przyjść w kolejności rozwiązywania tylko raz i po wszystkich swoich podklasach, tak aby nadpisać (to znaczy nadpisanie C przez członka x) faktycznie działają rozsądnie.

Jest to jeden z powodów, dla których należy unikać zajęć w starym stylu: wielokrotne dziedziczenie z wzorami "diamentowymi" po prostu nie działa z nimi rozsądnie, podczas gdy w nowym stylu.


147
2017-12-04 18:03



"[klasa przodków] A [jest] zmuszana do przyjrzenia się w kolejności rozwiązywania tylko raz i po wszystkich swoich podklasach, tak że przesłonięcia (to znaczy przesłonięcie elementu X przez członka x) działa właściwie." - Święto Trzech Króli! Dzięki temu zdaniu mogę ponownie wykonać MRO w mojej głowie. \ o / Dziękuję bardzo. - Esteis


Rozdzielczość metody Pythona jest w rzeczywistości bardziej złożona niż po prostu zrozumienie wzoru diamentu. Do naprawdę zrozum to, spójrz na Linearyzacja C3. Zauważyłem, że naprawdę pomaga używać instrukcji print podczas rozszerzania metod śledzenia zamówienia. Na przykład, jak myślisz, jaki byłby wynik tego wzoru? (Uwaga: "X" ma mieć dwie krawędzie przecinające, a nie węzeł, a ^ oznacza metody, które wywołują 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

Czy dostałeś A B D C E F G?

x = A()
x.m()

Po wielu próbach popełniłem błąd, wymyśliłem nieformalną interpretację teorii linearyzacji C3 w następujący sposób: (Ktoś proszę dać mi znać, jeśli to jest złe).

Rozważmy ten przykład:

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



Powinieneś poprawić swój drugi kod: umieściłeś klasę "I" jako pierwszą linię i również super, aby znalazła super klasę "G", ale "I" jest pierwszą klasą, więc nigdy nie będzie w stanie znaleźć klasy "G", ponieważ tam nie jest "G" górne "I". Ustaw klasę "I" między "G" i "F" :) - Ayodhyankit Paul
Przykładowy kod jest niepoprawny. super ma wymagane argumenty. - danny
Wewnątrz super definicji klasy () nie wymaga argumentów. Widzieć https://docs.python.org/3/library/functions.html#super - Ben
Twoja teoria wykresu jest niepotrzebnie skomplikowana. Po kroku 1 wstaw krawędzie z klas po lewej do klas po prawej (na dowolnej liście dziedziczenia), a następnie wykonaj sortowanie topologiczne i jesteś skończony. - Kevin
@ Kevin Nie sądzę, że to prawda. Idąc za moim przykładem, czy A C D B E F G H nie byłby właściwym topologicznym typem? Ale to nie jest kolejność rozwiązywania. - Ben


Wynik, jaki otrzymujesz, jest poprawny. Spróbuj zmienić klasę podstawową Base3 do Base1 i porównaj z tą samą hierarchią klas klasycznych:

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

Teraz generuje:

Base3
Base1

Czytać to wyjaśnienie po więcej informacji.


5
2017-12-04 17:46





Widzisz to zachowanie, ponieważ rozdzielczość metody jest głęboka, a nie pierwsza. Dziedzictwo Dervieda wygląda

         Base2 -> Base1
        /
Derived - Base3

Więc instance.amethod()

  1. Sprawdza Base2, nie znajduje amethod.
  2. Widzi, że Base2 odziedziczył po Base1 i sprawdza Base1. Base1 ma amethod, więc zostaje wywołany.

Znajduje to odzwierciedlenie w Derived.__mro__. Po prostu wykonaj iterację Derived.__mro__ i zatrzymaj się, gdy znajdziesz szukaną metodę.


0
2017-12-04 17:53



Wątpię, że powodem, dla którego dostaję "Base1" jako odpowiedź, jest to, że rozdzielczość metody jest na pierwszym miejscu, myślę, że jest w tym coś więcej niż podejście z głębi pierwszego. Zobacz przykład Denisa, jeśli była to pierwsza głębokość o / p powinna być "Base1". Odwołaj się również do pierwszego przykładu w podanym łączu, a także pokazany MRO wskazuje, że rozdzielczość metody nie jest określona tylko przez przejście w pierwszym rzędzie głębokości. - sateesh
Niestety link do dokumentu na MRO zapewnia Denis. Proszę to sprawdzić, pomyliłem się, że podałeś mi link do python.org. - sateesh
Jest to na ogół głęboka nowość, ale są mądrości do obsługi dziedzictwa diamentowego, jak wyjaśnił Alex. - jamessan