Section 11 / 12
メソッド解決順序(MRO)と mro() メソッド
「同じ名前のメソッドが親クラスにも子クラスにもあるとき、Pythonはどっちを使うの?」——こう感じたことはありませんか。
特に多重継承(親クラスが2つ以上)を使い始めると、この疑問は必ず出てきます。そんなときに欠かせないのが、メソッド解決順序(Method Resolution Order, MRO)という考え方です。MROは「メソッドや属性を探す順番」をはっきりと決めるルールで、Pythonのクラス設計を理解するうえで重要な土台になります。
この章では、PythonのMROが何をしているのか、そしてクラスのMROを確認できる mro() メソッドの使い方を、初心者にもわかりやすく解説します。多重継承を使う予定がない方でも、「なぜそのメソッドが呼ばれたのか」を説明できるようになると、デバッグや保守がぐっと楽になります。
MRO(メソッド解決順序)とは?
Pythonのオブジェクトは、属性やメソッドを探すときに、まず「そのインスタンスのクラス」を見て、次に「親クラス」を順番にたどっていきます。継承が1本のときは直感的ですが、多重継承では親が左右に分かれて複雑になりがちです。「どの親から先に探すのか」を一貫して決めてくれるのがMROです。
ポイントは、MROが一度決まると、そのクラスでの探索順は常に同じになること。これにより、同名メソッドが複数の親にあっても、Pythonは迷わず正しい順番で探してくれます。「思ったのと違うメソッドが呼ばれた!」というトラブルは、たいていMROを見れば理由がわかります。
C3リニアリゼーションをやさしくイメージ
PythonはMROを決めるために「C3リニアリゼーション」というアルゴリズムを使っています。名前は難しそうですが、イメージはシンプルです。
次のような感覚で覚えると理解しやすくなります。
- まずは「自分のクラス」を最優先で見る。
- 複数の親がいる場合は、クラス定義で書いた「左側の親」から先に見る(左優先)。
- すでに決まっている親クラスの順序はできるだけ崩さない(一貫性)。
この3つの考え方で、複雑な多重継承でも「矛盾のない一本の探索リスト(MRO)」に並べ替えてくれます。MROは決して適当ではなく、再現性のある決定的な順序です。
mro() メソッドと __mro__ 属性で順番を確認する
「結局どんな順番で探すの?」と迷ったら、クラスの mro() メソッドを呼び出してみましょう。MROをリストとして返してくれるので、探索の流れが目で見て確認できます。読み取り専用のタプルとしてアクセスできる __mro__ 属性もあります。
mro()… MROをリストで返す__mro__… MROをタプルで返す(読み取り専用)
サンプルコード:左優先の基本パターン
次のコードは、多重継承でよくある「B と C を親に持つ D」という構成です。どの greet が呼ばれるでしょうか?
class A:
def greet(self):
return "Hello from A"
class B(A):
def greet(self):
return "Hello from B"
class C(A):
def greet(self):
return "Hello from C"
class D(B, C):
pass
# DクラスのMROを確認
print(D.mro())
# => [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
d = D()
print(d.greet()) # => "Hello from B"
この結果を見ると、Pythonは D → B → C → A → object の順にメソッドを探すことがわかります。D には greet がないので、次に B を見に行き、B で greet を見つけた時点で探索は完了。だから出力は "Hello from B" になります。「なぜ C ではなく B なの?」と感じた方は、先ほどの「左優先」ルールを思い出してください。D(B, C) と書いた順番がそのまま効いています。
サンプルコード:親クラスの順番を入れ替えると?
では、親クラスの並びを逆にするとどうなるでしょう。直感通り結果も変わるのか、確認してみましょう。
class A:
def greet(self):
return "Hello from A"
class B(A):
def greet(self):
return "Hello from B"
class C(A):
def greet(self):
return "Hello from C"
class D2(C, B):
pass
print(D2.mro())
# => [<class '__main__.D2'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
d2 = D2()
print(d2.greet()) # => "Hello from C"
D2 の MRO は D2 → C → B → A → object です。今度は C が先に探索されるため、結果は "Hello from C"。同じ親たちでも、定義の並び順によって呼ばれるメソッドが変わることがはっきり確認できます。多重継承を設計するときは、この「並び順が意味を持つ」点を常に意識しましょう。
ダイヤモンド継承でも安心:MROが衝突を避ける
多重継承の有名な例に「ダイヤモンド継承」があります。A を共通の祖先として B と C があり、D が B と C を継承する形です。もし B と C がそれぞれ A のメソッドを上書きしていたら、D でどれを使えばいいのか迷いそうですよね。ここでもC3リニアリゼーションが、矛盾なく一つの順序にまとめてくれます。
大切なのは、「super() を使うチェーン」もこの順序に沿って動くということ。MROが安定しているからこそ、super() を使った協調的なメソッド呼び出しが安全に機能します。多重継承で super() を使う場合は、各クラスで同じメソッド名を呼び出し、必要なら最後に親へ委譲する、という統一的な書き方が効果的です。
mro() を使った実践的な確認方法
クラス設計やデバッグのとき、「どの順に見に行くのか」をその場で確認できると安心ですよね。そんなときは次のように、名前だけを抜き出して読むと見やすくなります。
class A:
def greet(self):
return "Hello from A"
class B(A):
def greet(self):
return "Hello from B"
class C(A):
def greet(self):
return "Hello from C"
class D(B, C):
pass
print([cls.__name__ for cls in D.mro()])
# => ['D', 'B', 'C', 'A', 'object']
print(D.__mro__)
# => (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
help(D) を使っても、クラスの情報と一緒にMROを確認できます。エディタやREPLで素早くチェックできるようにしておくと、継承まわりの不具合を早期に発見しやすくなります。
まとめ
ここまで学習した内容をまとめます。
- MRO(メソッド解決順序)は、Pythonがメソッドや属性をどの順番で探すかを定めるルールです。
- PythonはC3リニアリゼーションにもとづいて、左優先かつ一貫性のある順序を作ります。
- 実際の順序は mro() や mro で確認できます。多重継承やオーバーライドで迷ったら、まずここをチェックしましょう。
- 親クラスの並び順は結果に影響します。なぜそのメソッドが呼ばれるのか、MROを見れば必ず説明できます。
「今このメソッドはどこから来ているのだろう?」と迷ったら、mro() を一度実行してみてください。継承の見通しが良くなり、Pythonのクラス設計がもっと安心して扱えるようになります。