11.1 メソッド解決順序
メソッド解決順序 (Method Resolution Order, MRO) は、Pythonがクラスの階層内でメソッドや属性を探す順序を決定するものだよ。 これは特に、複数の親クラスから属性やメソッドを継承する場合に重要だね。
簡単に言うと、Pythonがクラスの継承ツリーを巡るための厳密な固定順序(というよりアルゴリズム)があるんだ。そのアルゴリズムは、メソッドの検索順序を正しく保証するよ。こんな感じで説明できる:
C3線形化アルゴリズム
C3線形化アルゴリズムは、MROを次のように組み合わせることで決定する:
- クラス自体。
- 親クラスのリスト(記述されている順序)。
- 親クラスのMRO(同じ順序)。
C3線形化アルゴリズムのルール
- ローカルメソッドの順序を維持: クラス
AがクラスBよりも前に指定されている場合、クラスAのすべてのメソッドはクラスBのメソッドよりも先に考慮されるべきだよ。 - 親クラスの順序を維持: クラス
AがクラスBの親である場合、クラスAのすべてのメソッドはクラスBのメソッドよりも先に考慮されるべきだよ。 - 継承順を考慮: クラス
Cが複数のクラスの親である場合、クラスCのメソッドの順序はこれらすべてのクラスのMROで維持されるべきだよ。
アルゴリズムのステップ:
ステップ1. クラス自体から始める:
常にそのメソッドが呼ばれたクラス自体から始めるんだ。
ステップ2. 基本クラスを記述された順序で追加:
現在のクラスの後で、継承時に指定された順序で基本クラスをチェックするんだ。
ステップ3. 親クラスを巡る:
その中でフィールドとメソッドを探すんだ。
ステップ4. 親クラスのMROを統合する:
同じ基本クラスが複数の経路で継承される場合、それは一度だけ正しい順序でチェックされる(他のすべての回はスキップされる)。
「アルゴリズムとデータ構造」のトピックに既に詳しい人には、これは幅優先探索ではなく、深さ優先探索だとわかるだろうね。
11.2 MROの検証
Pythonでは、__mro__属性やmro()関数を使って、クラスのメソッドやフィールドの巡回順序を確認できるよ。
例:
class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
class C(A):
def method(self):
print("C")
class D(B, C):
def method(self):
print("D")
# MROの検証
print(D.__mro__)
出力は次のようになるよ:
(<class '__main__.D'>,
<class '__main__.B'>,
<class '__main__.C'>,
<class '__main__.A'>,
<class 'object'>)
これは、Pythonがメソッドや属性をどの順序で探すかを示しているよ:
D: まずPythonはクラスDでメソッドを探すよ。-
B: 次にPythonはクラスB(最初の親クラス)でメソッドを探すよ。 -
C: クラスBでメソッドが見つからない場合、PythonはクラスC(2番目の親クラス)でメソッドを探すよ。 -
A: クラスBとCでメソッドが見つからない場合、PythonはクラスAでメソッドを探すよ。 -
object: 最終的に、Pythonは基本クラスobjectでメソッドを探すよ。
11.3 super() を使用したMRO
関数 super() は、親クラスのメソッドを正しい順序で呼び出すためにMROに従うんだ。
super()の使用例を見てみよう:
class A:
def method(self):
print("A")
super().method()
class B(A):
def method(self):
print("B")
super().method()
class C(A):
def method(self):
print("C")
super().method()
class D(B, C):
def method(self):
print("D")
super().method()
d = D()
d.method()
出力は次のようになるよ:
D
B
C
A
巡回順序 (MRO)
1. クラスDのメソッドmethodを呼び出す:
- Pythonはまずクラス
Dでメソッドを探し、そこに見つけるよ。 - メソッド
D.method()は実行され、"D"を出力するよ。 - 次に
super().method()が呼ばれ、次のメソッドを呼び出すためにMROに従うんだ。
2. クラスBのメソッドmethodを呼び出す:
- MROによると、次のクラスは
Dの後にBだよ。 - メソッド
B.method()は実行され、"B"を出力するよ。 - 次に
super().method()が呼ばれ、次のメソッドを呼び出すためにMROに従うんだ。
3. クラスCのメソッドmethodを呼び出す:
- MROで
Bの後に来るのはCだよ。 - メソッド
C.method()は実行され、"C"を出力するよ。 - 次に
super().method()が呼ばれ、次のメソッドを呼び出すためにMROに従うんだ。
4. クラスAのメソッドmethodを呼び出す:
- MROで
Cの後に来るのはAだよ。 - メソッド
A.method()は実行され、"A"を出力するよ。 - その後
super().method()が呼ばれるけど、Aには親のmethod(objectを除く)がないので、呼び出しはこれ以上の処理なしで終了するよ。
GO TO FULL VERSION