Soru Yeni stil sınıflarında Yöntem Çözünürlük Düzeni (MRO)?


Kitapta Nutshell Python (2nd Edition) kullanan bir örnek var
klasik çözünürlük düzeninde yöntemlerin nasıl çözümlendiğini göstermek için eski stil sınıfları
yeni düzende nasıl farklıdır.

Örneği yeni tarzda yeniden yazarak aynı örneği denedim, ancak sonuç eski stil sınıflarıyla elde edilenlerden farklı değil. Örneği çalıştırmak için kullanıyorum python sürümü 2.5.2. Örnek aşağıdadır:

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__  

Arama instance.amethod() baskılar Base1ama MRO'yu yeni tarz sınıflarıyla anladığım kadarıyla çıktı olmalıydı. Base3. Arama Derived.__mro__ baskılar:

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

Yeni stil sınıflarıyla MRO anlayışımın yanlış mı, yoksa tespit edemediğim aptalca bir hata yaptığımı bilmiyorum. Lütfen MRO'nun daha iyi anlaşılmasında bana yardımcı olun.


76
2017-12-04 17:30


Menşei




Cevaplar:


Mirasın yeni-stil sınıflar için çözüm sırası arasındaki çok önemli fark, aynı atası sınıfının "naif", derinlik-ilk yaklaşımda bir kereden fazla meydana gelmesidir - örneğin, bir "elmas mirası" durumunda düşünün:

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

Burada, miras tarzı, çözünürlük sırası D - B - A - C - A'dır: D.x'e bakıldığında, A, çözülme sırasına göre çözülme sırasındaki ilk tabandır, böylece C'deki tanımı gizler.

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

Burada, yeni tarz, sipariş:

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

ile A tüm alt sınıflarının yalnızca bir kez ve sonra karar mertebesine gelmeye zorlandığını, yani C'nin üyeyi geçersiz kıldığını x) gerçekten duyarlı çalışmak.

Eski tarz sınıflardan kaçınılması gereken nedenlerden biri: "elmas benzeri" kalıplarla birden fazla miras, yeni tarzla birlikte, onlarla hassas bir şekilde çalışmaz.


147
2017-12-04 18:03



"[atası sınıfı] A, bütün alt sınıflarının yalnızca bir kez ve sonra karar mertebesine gelmeye zorlanır, böylece geçersiz kılar (diğer bir deyişle, C'nin üye x'in geçersiz kılınması) aslında mantıklı bir şekilde çalışır." - Epifani! Bu cümle sayesinde yine kafamda MRO yapabilirim. \ Çok teşekkür ederim. - Esteis


Python'un yöntem çözümleme sırası aslında elmas desenini anlamaktan daha karmaşıktır. için Gerçekten mi anlamak, bir göz at C3 doğrusallaştırma. Siparişi takip etmek için yöntemleri genişletirken, baskı ifadelerinin kullanılmasına gerçekten yardımcı olduğunu buldum. Örneğin, bu modelin çıktısının ne olacağını düşünüyorsunuz? (Not: 'X', iki geçiş kenarı olduğunu, bir düğüm olmadığını ve ^ (super) çağrılan yöntemleri ifade ettiğini varsayalım.

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

A B D C E F G aldın mı?

x = A()
x.m()

Çok fazla deneme yaptıktan sonra, C3 doğrusallaştırmanın gayri resmi bir grafik teorisini şu şekilde yorumladım: (Birisi bunun yanlış olup olmadığını bana bildirin).

Bu örneği düşünün:

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



İkinci kodunuzu düzeltmelisiniz: "I" sınıfını birinci sıraya koydunuz ve süper kullandıkları için süper "G" sınıfını buldunuz, ancak "I" birinci sınıf oldu, çünkü "G" sınıfını asla bulamayacaksınız. "G" üst "I" değil. "G" ve "F" arasında sınıf "I" koyun :) - Ayodhyankit Paul
Örnek kod yanlış. super gerekli argümanlara sahip. - danny
Bir sınıf tanımı içinde super () argüman gerektirmez. Görmek https://docs.python.org/3/library/functions.html#super - Ben
Grafik teoriniz gereksiz bir şekilde karmaşıktır. 1. adımdan sonra, soldaki sınıflardan sağdaki sınıflara (herhangi bir kalıtım listesinde) kenar ekleyin ve ardından bir topolojik sıralama ve işin bitti. - Kevin
@Kevin Doğru olduğunu sanmıyorum. Örneğimi takip etmem, geçerli bir topolojik sıralama mıdır? Ama bu çözüm kararı değil. - Ben


Aldığınız sonuç doğru. Temel sınıfı değiştirmeyi deneyin Base3 için Base1 ve klasik sınıflar için aynı hiyerarşi ile karşılaştırın:

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

Şimdi çıktılar:

Base3
Base1

okumak bu açıklama daha fazla bilgi için.


5
2017-12-04 17:46





Bu davranışı görüyorsunuz, çünkü yöntem çözünürlüğü birinci sınıf değil, önce derinlik ilkesidir. Dervied'in mirası benziyor

         Base2 -> Base1
        /
Derived - Base3

Yani instance.amethod()

  1. Çekler Base2, amethod bulamıyor.
  2. Base2'nin Base1'den miras aldığını ve Base1'i kontrol ettiğini görür. Base1 bir amethodBöylece çağrılır.

Bu yansıyan Derived.__mro__. Sadece yinelemek Derived.__mro__ ve aranan yöntemi bulduğunuzda durun.


0
2017-12-04 17:53



Cevap olarak "Base1" almamın nedeninin, yöntem çözümünün derinlikli olması gerektiğinden şüphe ettiğimi düşünüyorum, bence derinlemesine bir ilk yaklaşımdan daha fazlası var. Önce Denis'in örneği, eğer ilk derinlik olsaydı, "Base1" olmalıydı. Ayrıca, sağladığınız bağlantıdaki ilk örneğe bakın, ayrıca gösterilen MRO, yöntem çözümünün sadece birinci derece sırada geçiş yaparak belirlenmediğini gösterir. - sateesh
Maalesef MRO belgesine olan link Denis tarafından sağlanıyor. Lütfen, bana python.org'un bağlantısını verdiğinizi yanlış anladım. - sateesh
Genel olarak derinliklidir, ama Alex'in açıkladığı gibi elmas benzeri mirasın üstesinden gelmek için akıllılar vardır. - jamessan