Pertanyaan Method Resolution Order (MRO) dalam kelas-kelas gaya baru?


Di dalam buku Python in a Nutshell (Edisi Kedua) ada contoh yang menggunakan
kelas gaya lama untuk menunjukkan bagaimana metode diselesaikan dalam urutan resolusi klasik dan
apa bedanya dengan orde baru.

Saya mencoba contoh yang sama dengan menulis ulang contoh dalam gaya baru tetapi hasilnya tidak berbeda dari apa yang diperoleh dengan kelas gaya lama. Versi python yang saya gunakan untuk menjalankan contoh adalah 2.5.2. Di bawah ini adalah contohnya:

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__  

Panggilan instance.amethod() cetakan Base1, tetapi sesuai pemahaman saya tentang MRO dengan gaya baru dari kelas output seharusnya Base3. Panggilan Derived.__mro__ cetakan:

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

Saya tidak yakin apakah pemahaman saya tentang MRO dengan kelas-kelas gaya baru tidak benar atau bahwa saya melakukan kesalahan konyol yang tidak dapat saya deteksi. Tolong bantu saya untuk lebih memahami MRO.


75
2017-12-04 17:30


asal


Jawaban:


Perbedaan penting antara urutan resolusi untuk kelas baru vs warisan muncul ketika kelas leluhur yang sama muncul lebih dari satu kali dalam pendekatan "naif", mendalam-pertama - mis., Mempertimbangkan kasus "warisan berlian":

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

di sini, legacy-style, urutan resolusinya adalah D - B - A - C - A: jadi ketika mencari D.x, A adalah basis pertama dalam urutan resolusi untuk menyelesaikannya, sehingga menyembunyikan definisi dalam C. Sementara:

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

di sini, gaya baru, pesanannya adalah:

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

dengan A dipaksa untuk datang dalam urutan resolusi hanya sekali dan setelah semua subclassnya, sehingga menimpa (yaitu, penggantian C dari anggota x) benar-benar bekerja dengan bijaksana.

Ini adalah salah satu alasan mengapa kelas gaya lama harus dihindari: pewarisan berganda dengan pola "berlian seperti" tidak bekerja dengan sepatutnya dengan mereka, sementara itu dengan gaya baru.


147
2017-12-04 18:03



Perintah resolusi metode Python sebenarnya lebih kompleks daripada hanya memahami pola berlian. Untuk sangat memahaminya, lihatlah Linearisasi C3. Saya telah menemukan itu sangat membantu untuk menggunakan pernyataan cetak ketika memperluas metode untuk melacak pesanan. Misalnya, menurut Anda apa hasil dari pola ini? (Catatan: 'X' dianggap menjadi dua persimpangan tepi, bukan node dan ^ menandakan metode yang memanggil 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

Apakah Anda mendapat A B D C E F G?

x = A()
x.m()

Setelah banyak percobaan kesalahan, saya datang dengan interpretasi teori grafik informal dari linierisasi C3 sebagai berikut: (Seseorang tolong beritahu saya jika ini salah.)

Pertimbangkan contoh ini:

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



Hasil yang Anda dapatkan benar. Coba ubah kelas dasar Base3 untuk Base1 dan bandingkan dengan hierarki yang sama untuk kelas klasik:

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

Sekarang hasilnya:

Base3
Base1

Baca baca penjelasan ini untuk informasi lebih lanjut.


5
2017-12-04 17:46



Anda melihat perilaku itu karena resolusi metode mendalam-pertama, bukan yang pertama. Peninggalan turunan Dervied terlihat seperti

         Base2 -> Base1
        /
Derived - Base3

Begitu instance.amethod()

  1. Cek Base2, tidak menemukan metode.
  2. Melihat bahwa Base2 telah mewarisi dari Base1, dan memeriksa Base1. Base1 memiliki amethod, jadi itu dipanggil.

Ini tercermin dalam Derived.__mro__. Cukup ulangi Derived.__mro__ dan berhenti ketika Anda menemukan metode yang dicari.


0
2017-12-04 17:53