「同じ名前のメソッドが親クラスにも子クラスにもあるとき、Pythonはどっちを使うの?」——こう感じたことはありませんか。
特に多重継承(親クラスが2つ以上)を使い始めると、この疑問は必ず出てきます。そんなときに欠かせないのが、メソッド解決順序(Method Resolution Order, MRO)という考え方です。MROは「メソッドや属性を探す順番」をはっきりと決めるルールで、Pythonのクラス設計を理解するうえで重要な土台になります。
この章では、PythonのMROが何をしているのか、そしてクラスのMROを確認できる mro() メソッドの使い方を、初心者にもわかりやすく解説します。多重継承を使う予定がない方でも、「なぜそのメソッドが呼ばれたのか」を説明できるようになると、デバッグや保守がぐっと楽になります。
Pythonのオブジェクトは、属性やメソッドを探すときに、まず「そのインスタンスのクラス」を見て、次に「親クラス」を順番にたどっていきます。継承が1本のときは直感的ですが、多重継承では親が左右に分かれて複雑になりがちです。「どの親から先に探すのか」を一貫して決めてくれるのがMROです。
ポイントは、MROが一度決まると、そのクラスでの探索順は常に同じになること。これにより、同名メソッドが複数の親にあっても、Pythonは迷わず正しい順番で探してくれます。「思ったのと違うメソッドが呼ばれた!」というトラブルは、たいていMROを見れば理由がわかります。
PythonはMROを決めるために「C3リニアリゼーション」というアルゴリズムを使っています。名前は難しそうですが、イメージはシンプルです。
次のような感覚で覚えると理解しやすくなります。
この3つの考え方で、複雑な多重継承でも「矛盾のない一本の探索リスト(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"。同じ親たちでも、定義の並び順によって呼ばれるメソッドが変わることがはっきり確認できます。多重継承を設計するときは、この「並び順が意味を持つ」点を常に意識しましょう。
多重継承の有名な例に「ダイヤモンド継承」があります。A を共通の祖先として B と C があり、D が B と C を継承する形です。もし B と C がそれぞれ A のメソッドを上書きしていたら、D でどれを使えばいいのか迷いそうですよね。ここでもC3リニアリゼーションが、矛盾なく一つの順序にまとめてくれます。
大切なのは、「super() を使うチェーン」もこの順序に沿って動くということ。MROが安定しているからこそ、super() を使った協調的なメソッド呼び出しが安全に機能します。多重継承で super() を使う場合は、各クラスで同じメソッド名を呼び出し、必要なら最後に親へ委譲する、という統一的な書き方が効果的です。
クラス設計やデバッグのとき、「どの順に見に行くのか」をその場で確認できると安心ですよね。そんなときは次のように、名前だけを抜き出して読むと見やすくなります。
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のクラス設計がもっと安心して扱えるようになります。