Yeni stil sınıflarında Yöntem Çözümleme Sırası (MRO)?


100

Python in a Nutshell (2nd Edition) kitabında ,
yöntemlerin klasik çözünürlük sırasına göre
nasıl çözüldüğünü ve yeni düzende nasıl farklı olduğunu göstermek için eski stil sınıflarını kullanan bir örnek var .

Örneği yeni üslupta yeniden yazarak aynı örneği denedim ama sonuç eski tarz sınıflarla elde edilenden farklı değil. Örneği çalıştırmak için kullandığım 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__  

Çağrı instance.amethod()yazdırılır Base1, ancak MRO ile ilgili yeni sınıf stillerini anladığıma göre çıktının olması gerekirdi Base3. Çağrı Derived.__mro__yazdırılır:

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

Yeni stil sınıfları ile MRO anlayışımın yanlış olup olmadığından ya da tespit edemediğim aptalca bir hata yaptığımdan emin değilim. Lütfen MRO'yu daha iyi anlamama yardımcı olun.

Yanıtlar:


188

Eski ve yeni stil sınıfları için çözünürlük sıralaması arasındaki önemli fark, aynı üst sınıf "naif", derinlikli yaklaşımda birden fazla kez ortaya çıktığında ortaya çıkar - örneğin, "elmas kalıtım" durumunu 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, eski stil, çözünürlük sırası D - B - A - C - A şeklindedir: Dx'i ararken, A çözünürlük sırasındaki ilk temeldir, dolayısıyla 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ş şu şekildedir:

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

ile A, sadece bir kez ve alt sınıflarından sonuçta çözünürlüğü sırayla gelmek zorunda böylece geçersiz kılmaları (üyesinin C'nin geçersiz kılma yani o x) aslında makul çalışır.

Eski stil sınıflardan kaçınılması gereken nedenlerden biri de bu: "Elmas benzeri" desenlere sahip çoklu kalıtım, yeni stilde olduğu halde, onlarla mantıklı bir şekilde çalışmıyor.


2
"[ata sınıfı] A, tüm alt sınıflarından sonra yalnızca bir kez ve sonra çözüm sırasına girmeye zorlanır, böylece geçersiz kılmalar (yani, C'nin üye x'i geçersiz kılması) mantıklı bir şekilde çalışır." - Epifani! Bu cümle sayesinde kafamda tekrar MRO yapabilirim. \ o / Çok teşekkür ederim.
Esteis

26

Python'un yöntem çözünürlük sıralaması aslında sadece elmas desenini anlamaktan daha karmaşıktır. Bunu gerçekten anlamak için C3 doğrusallaştırmasına bir göz atın . Siparişi takip etmek için yöntemleri genişletirken baskı ifadelerini kullanmanın gerçekten yardımcı olduğunu gördüm. Örneğin, bu modelin çıktısının ne olacağını düşünüyorsunuz? (Not: 'X'in bir düğüm değil, kesişen iki kenar olduğu varsayılır ve ^, super () öğesini çağıran yöntemleri belirtir)

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

ABDCEFG aldın mı?

x = A()
x.m()

Birçok deneme yanılmasından sonra, C3 doğrusallaştırmasının gayri resmi bir grafik teorisi yorumunu şu şekilde buldum: (Birisi lütfen bunun yanlış olup olmadığını bildirin.)

Şu ö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()

İkinci kodunuzu düzeltmelisiniz: "I" sınıfını birinci satır olarak koydunuz ve aynı zamanda süper kullandınız, böylece "G" süper sınıfını buluyorsunuz ama "I" birinci sınıf, bu nedenle "G" sınıfını asla bulamayacak çünkü orada "G" üst "I" değildir. "I" sınıfını "G" ve "F" arasına koyun :)
Aaditya Ura

Örnek kod yanlış. supergerekli argümanlar var.
danny

2
Bir sınıf tanımının içinde super () bağımsız değişkenler gerektirmez. Bkz. Https://docs.python.org/3/library/functions.html#super
Ben

Grafik teoriniz gereksiz yere karmaşıktır. 1. adımdan sonra, soldaki sınıflardan sağdaki sınıflara (herhangi bir miras listesinde) kenarlar ekleyin ve ardından topolojik bir sıralama yapın ve bitirdiniz.
Kevin

@Kevin Bunun doğru olduğunu sanmıyorum. Örneğime göre, ACDBEFGH geçerli bir topolojik sıralama olmaz mı? Ancak bu çözüm sırası değil.
Ben

5

Aldığınız sonuç doğrudur. Taban sınıfını değiştirmeyi deneyin Base3için Base1ve klasik sınıflar için aynı hiyerarşi ile karşılaştır:

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ı:

Base3
Base1

Daha fazla bilgi için bu açıklamayı okuyun .


1

Bu davranışı görüyorsunuz çünkü yöntem çözünürlüğü en başta değil, derinliklidir. Dervied'in mirası gibi görünüyor

         Base2 -> Base1
        /
Derived - Base3

Yani instance.amethod()

  1. Base2'yi denetler, bir yöntem bulamaz.
  2. Base2'nin Base1'den miras aldığını görür ve Base1'i kontrol eder. Base1 a sahiptir amethod, bu yüzden çağrılır.

Bu yansıtılır Derived.__mro__. Basitçe tekrarlayın Derived.__mro__ve aranan yöntemi bulduğunuzda durun.


Cevap olarak "Base1" i almamın nedeninin yöntem çözünürlüğünün derinlikli olması olduğundan şüpheliyim, bence bence derinlikli bir yaklaşımdan daha fazlası var. Denis'in örneğine bakın, eğer derinlik olsaydı ilk o / p "Base1" olmalıydı. Ayrıca, sağladığınız bağlantıdaki ilk örneğe bakın, burada gösterilen MRO da, yöntem çözünürlüğünün yalnızca derinlemesine birinci sırada gezinerek belirlenmediğini belirtir.
sateesh

MRO ile ilgili belgenin bağlantısı Denis tarafından sağlandı. Lütfen kontrol edin, bana python.org bağlantısını verdiğinizi yanlış anladım.
sateesh

4
Genelde derinlik önemlidir, ancak Alex'in açıkladığı gibi elmas benzeri kalıtımla başa çıkmanın akıllıca yanları vardır.
jamessan
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.