Python __mro__の理解:継承の順序、メソッド解決順序(MRO)をマスターする!


 

Pythonでオブジェクト指向プログラミングを行う際、クラスの継承は非常に重要な概念です。しかし、複数のクラスを継承する多重継承を使った場合、「どの親クラスのメソッドが呼び出されるんだろう?」と疑問に思ったことはありませんか?その疑問を解決してくれるのが、クラスが持つ特別な属性**__mro__**(Method Resolution Order: メソッド解決順序)です。__mro__を理解することで、Pythonがどのようにメソッドや属性を探索するかを正確に把握し、多重継承における予期せぬ挙動を防ぐことができます。


 

__mro__とは何か?

 

__mro__は、クラスが持つタプルの属性で、そのクラスと継承階層にあるすべての基底クラスが、メソッドや属性を探索する順序で格納されています。この順序は、特に多重継承において、どの親クラスのメソッドが優先的に呼び出されるかを決定します。

Pythonは、メソッドや属性を探索する際に、C3線形化アルゴリズム(C3 Linearization)というアルゴリズムに基づいて__mro__を構築します。これにより、多重継承であっても一貫性のある解決順序が保証されます。


 

__mro__の基本的な確認方法

 

__mro__は、クラスオブジェクトの属性として直接アクセスするか、inspectモジュールのgetmro()関数、またはhelp()関数を通じて確認できます。

 

1. クラス名.__mro__で直接アクセス

 

最も一般的な方法です。

 

コード例

 

Python
 
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): # BとCを多重継承
    def method(self):
        print("Dのメソッド")

print(f"DクラスのMRO: {D.__mro__}")
# 出力例: (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

上記の出力から、Dクラスのインスタンスでmethod()を呼び出すと、以下の順序で探索されることがわかります。

  1. Dクラス自身

  2. Bクラス

  3. Cクラス

  4. Aクラス

  5. objectクラス(すべてのクラスの基底クラス)

実際にメソッドを呼び出すと、この順序に従ってDクラスのmethodが呼び出されます。

Python
 
d = D()
d.method() # 出力: Dのメソッド

もしDmethodがなければ、Bmethodが呼び出され、BにもなければCmethodが、といった具合に探索されます。

Python
 
class D2(B, C): # D2にはmethodがない
    pass

d2 = D2()
d2.method() # 出力: Bのメソッド (BがCより先にMROにあるため)

 

2. super()__mro__の関係

 

__mro__を理解することは、super()関数の動作を理解する上でも不可欠です。super()は、MROに従って「次に呼び出すべき親クラスのメソッド」を探し出してくれます。これにより、多重継承においても適切に親クラスのメソッドを呼び出すことができます。

 

コード例

 

Python
 
class Base:
    def greet(self):
        print("Hello from Base")

class Mixin1(Base):
    def greet(self):
        super().greet()
        print("Hello from Mixin1")

class Mixin2(Base):
    def greet(self):
        super().greet()
        print("Hello from Mixin2")

class FinalClass(Mixin1, Mixin2):
    def greet(self):
        super().greet()
        print("Hello from FinalClass")

print(f"FinalClassのMRO: {FinalClass.__mro__}")
# 出力例: (<class '__main__.FinalClass'>, <class '__main__.Mixin1'>, <class '__main__.Mixin2'>, <class '__main__.Base'>, <class 'object'>)

f_obj = FinalClass()
f_obj.greet()
# 出力:
# Hello from Base
# Hello from Mixin2
# Hello from Mixin1
# Hello from FinalClass

この出力順序は、FinalClassのMROに従ってsuper().greet()が連鎖的に呼び出されている結果です。

  1. FinalClass.greet()super().greet()を呼び出す

  2. MROの次のクラスであるMixin1.greet()が呼び出される。Mixin1.greet()super().greet()を呼び出す。

  3. MROの次のクラスであるMixin2.greet()が呼び出される。Mixin2.greet()super().greet()を呼び出す。

  4. MROの次のクラスであるBase.greet()が呼び出される。Base.greet()にはsuper()がないため、ここでBaseの出力がされる。

  5. 呼び出し元に戻り、Mixin2の出力、Mixin1の出力、FinalClassの出力が順に行われる。


 

__mro__の構築ルール(C3線形化アルゴリズム)

 

C3線形化アルゴリズムの詳細は複雑ですが、基本的な原則は以下の通りです。

  1. 子クラスは親クラスより前に来る。

  2. 複数の親クラスがある場合、親クラスがbasesタプルで指定された順序で来る。

  3. 共通の親クラスは、MRO内で一度しか出現せず、最も派生したクラスに近い位置に置かれる。

これにより、MROは常に一意で、一貫性があり、かつ継承グラフ内で単調に増加する(つまり、子クラスのMROが親クラスのMROの「後ろ」に親クラスの要素を保持する)ように構築されます。

もしPythonが有効なMROを構築できない場合(継承の順序に矛盾がある場合など)、TypeError: Cannot create a consistent method resolution order (MRO)のようなエラーが発生します。


 

まとめ

 

Pythonの**__mro__属性**は、クラスの継承階層におけるメソッドや属性の探索順序を示す非常に重要な概念です。特に多重継承を使用する際には、この__mro__の順序を理解することが、予期せぬ動作を避け、super()関数を正しく利用するために不可欠です。

__mro__を確認することで、Pythonが内部的にどのようにオブジェクトを解決しているかを把握でき、より堅牢で理解しやすいオブジェクト指向コードを書く手助けとなるでしょう。複雑な継承構造を持つクラスを設計する際は、常に__mro__を意識し、意図した通りの振る舞いをするか確認することをお勧めします。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座