新しいスタイルのクラスのメソッド解決順序(MRO)?


94

Python in the Nutshell(2nd Edition)』には、
古いスタイルのクラスを使用して、メソッドが従来の解決順序で解決される方法
と、新しい順序でどのように異なるかを示す例があります。

同じスタイルの例を新しいスタイルで書き直してみましたが、結果は古いスタイルのクラスで得られたものと変わりません。この例を実行するために使用しているpythonのバージョンは2.5.2です。以下に例を示します。

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__  

コール・instance.amethod()プリントBase1が、出力がされている必要がありますクラスの新しいスタイルとMROの私の理解あたりとして、Base3。呼び出しはDerived.__mro__印刷します:

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

新しいスタイルクラスを使用したMROの理解が間違っているのか、または検出できない愚かな間違いをしているのかわかりません。MROの理解を深めてください。

回答:


183

レガシークラスと新しいスタイルのクラスの解決順序の決定的な違いは、「素朴な」深さ優先アプローチで同じ祖先クラスが複数回発生する場合に発生します。たとえば、「ダイヤモンド継承」の場合を考えます。

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

ここでは、レガシースタイルの解決順序はD-B-A-C-Aです。したがって、Dxを検索するとき、Aはそれを解決するための解決順序の最初のベースであり、それによってCの定義を非表示にします。

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

ここでは、新しいスタイルで、順序は次のとおりです。

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

A、一度だけとそのサブクラスのすべての後に解像度のために来ることを余儀なくさそうオーバーライドは(メンバーのCのオーバーライドは、すなわちことをx)実際に賢明に働きます。

これは、古いスタイルのクラスを回避する必要がある理由の1つです。「ダイヤモンドのような」パターンを使用した多重継承は、新しいスタイルでは機能しますが、それらを使用するとうまく機能しません。


2
「[祖先クラス] Aは、すべてのサブクラスの後に一度だけ解決順序で来るように強制されるため、オーバーライド(つまり、Cのメンバーxのオーバーライド)は実際には適切に機能します。」- エピファニー!この文章のおかげで、私は再び頭の中でMROを行うことができます。\ o /ありがとうございます。
Esteis 2015

23

Pythonのメソッド解決の順序は、実際にはダイヤモンドパターンを理解するよりも複雑です。それを本当に理解するには、C3線形化を見てください。メソッドを拡張して順序を追跡するときに、printステートメントを使用すると本当に役立つことがわかりました。たとえば、このパターンの出力はどうなると思いますか?(注:「X」はノードではなく2つの交差するエッジであると想定され、^は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

ABDCEFGを入手しましたか?

x = A()
x.m()

多くの試行錯誤を繰り返した後、C3線形化の非公式グラフ理論の解釈を次のように思い付きました(これが間違っている場合は誰かに知らせてください)。

この例を考えてみましょう:

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

2番目のコードを修正する必要があります。クラス「I」を最初の行として配置し、superを使用してスーパークラス「G」を見つけましたが、「I」が最初のクラスなので「G」クラスを見つけることができません。 「G」アッパー「I」ではありません。クラス「I」を「G」と「F」の間に配置します:)
Aaditya Ura 2016

サンプルコードは正しくありません。super必須の引数があります。
ダニー

2
クラス定義の中では、super()は引数を必要としません。https://docs.python.org/3/library/functions.html#super
Ben

あなたのグラフ理論は不必要に複雑です。手順1の後、左側のクラスから右側のクラス(継承リスト内)にエッジを挿入し、トポロジカルソートを実行すれば完了です。
ケビン、

@ケビン私はそれが正しいとは思わない。私の例に続いて、ACDBEFGHは有効なトポロジカルソートではありませんか?しかし、それは解決の順序ではありません。
Ben

5

あなたが得る結果は正しいです。の基本クラスをBase3toに変更Base1して、クラシッククラスの同じ階層と比較してみてください。

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

今それは出力します:

Base3
Base1

詳細についてはこの説明をお読みください。


1

メソッドの解決が深さ優先ではなく幅優先であるため、この動作が見られます。Derviedの継承は次のようになります

         Base2 -> Base1
        /
Derived - Base3

そう instance.amethod()

  1. Base2をチェックし、メソッドが見つかりません。
  2. Base2がBase1から継承したことを確認し、Base1をチェックします。Base1にはがあるamethodため、呼び出されます。

これはに反映されていDerived.__mro__ます。単に繰り返し処理をDerived.__mro__行い、探しているメソッドが見つかったら停止します。


答えとして「Base1」を取得する理由は、メソッドの解決が深さ優先であるためかどうか疑問です。深さ優先アプローチよりもそれがあると思います。デニスの例を参照してください。深度が最初であれば、o / pは「Base1」である必要があります。また、提供したリンクの最初の例を参照してください。MROには、メソッドの解像度が深さ優先順でトラバースするだけでは決定されないことも示されています。
sateesh 2009

MRO上のドキュメントへのリンクはDenisによって提供されています。それを確認してください。python.orgへのリンクを提供していただいたのと間違えました。
sateesh

4
それは一般に深さ優先ですが、アレックスが説明したように、ダイヤモンドのような継承を処理する賢い方法があります。
jamessan
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.