順番に読み進めながら学べます

多重継承と MRO — 複数の親クラスを継承する

Python の多重継承を解説します。class 子(親A, 親B): で複数の親クラスを継承する書き方、メソッドの探索順序を決める MRO、__mro__ での確認方法までを図解で押さえます。

前回で 1 つの親クラスを継承する書き方とオーバーライド・super() までを整理しました。Python では 複数の親クラスを同時に継承する こともできます。これを 多重継承 と呼びます。今回は多重継承の書き方と、複数の親に同じ名前のメソッドがあるときに どの親が呼ばれるか を決めるルール — メソッド解決順序(MRO) — を見ていきます。

多重継承の基本構文

多重継承の書き方は単純で、親クラスをカンマ区切り で並べるだけです。class Duck(Animal, Swimmer, Flyer): のように書けば、DuckAnimal / Swimmer / Flyerすべての属性とメソッドを引き継ぎ ます。

下の例では、Animal が名前を持って鳴く役割、Swimmer が泳ぐ役割、Flyer が飛ぶ役割をそれぞれ持っており、Duck がその 3 つを束ねて全部できる存在になっています。

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass


class Swimmer:
    def swim(self):
        return f"{self.name} is swimming"


class Flyer:
    def fly(self):
        return f"{self.name} is flying"


class Duck(Animal, Swimmer, Flyer):    # 3 つを多重継承
    def speak(self):
        return f"{self.name} says quack"


duck = Duck("Donald")
print(duck.speak())   # Donald says quack
print(duck.swim())    # Donald is swimming
print(duck.fly())     # Donald is flying
Duck が 3 つの親を継承する
Animalname / speakSwimmerswimFlyerflyDuckspeak (override)継承継承継承
DuckAnimal から 属性と __init__Swimmer から swimFlyer から fly をそれぞれ引き継ぐ。speak だけは Duck 自身でオーバーライド。

Duck が 3 つの親クラスを束ねるコードを書きます。

class Animal: を定義し、__init__(self, name)self.name = name を代入してください。

class Swimmer: の中に swim(self) を定義し、return f"{self.name} is swimming" を返してください。Flyer も同様に fly(self) を定義してください。

class Duck(Animal, Swimmer, Flyer): を定義し、speak(self)return f"{self.name} says quack" を返してください。

duck = Duck("Donald") を作って speak() / swim() / fly() の 3 つの戻り値を print してください。

(正しく実行できれば解説が表示されます)

Python エディタ

コードを実行してください

メソッド解決順序(MRO) — どの親が優先されるか

問題は、複数の親に同じ名前のメソッド がある場合です。たとえば SwimmerFlyer の両方に move メソッドがあったら、Duck インスタンスから move を呼んだとき、どちらが実行されるのでしょうか?

Python は メソッド解決順序(Method Resolution Order, MRO) という決まった順序でクラスを上から探していき、最初に見つかったメソッドを実行します。多重継承の場合、class Duck(A, B, C): と書いた左から右の順 で探されます — つまり A の同名メソッドが優先され、A になければ B、それでもなければ C を見にいきます。

class Swimmer:
    def move(self):
        return "swimming"

class Flyer:
    def move(self):
        return "flying"

class Duck(Swimmer, Flyer):    # 左から優先 = Swimmer.move が勝つ
    pass

class Goose(Flyer, Swimmer):   # 順番を入れ替えると結果が変わる
    pass

print(Duck().move())   # swimming
print(Goose().move())  # flying
MRO は (A, B, C) の左から探す
Duck().move()Duck 自身に move?なし→ 次へあり→ 実行Swimmerに move?あり→ 実行
Duck(Swimmer, Flyer)Swimmer → Flyer の順 に探すので、両方に move があれば Swimmer の move が呼ばれる。順序を入れ替えれば結果も変わる。

実際の探索順は、CPython であれば クラス名.__mro__ で確認できます(本ページの実行環境は MicroPython なので __mro__ 属性は利用できません。手元の Python で試す場合のために結果イメージを下に載せておきます)。

# CPython で実行した場合のイメージ
class Swimmer:
    pass
class Flyer:
    pass
class Duck(Swimmer, Flyer):
    pass

for cls in Duck.__mro__:
    print(cls.__name__)
# Duck
# Swimmer
# Flyer
# object

リストの最後の object は何?

Python のすべてのクラスは、最終的に 組み込みクラス object を親に持ちます。明示的に書かなくても、class Foo: は内部的には class Foo(object): と同じです。だから __mro__ の末尾には必ず object が現れます。

親クラスの 書き順 が結果を変えることを、実際に動かして確認します。

class Swimmer: の中に move(self) を定義し、return "swimming" を返してください。Flyer も同様に move を定義し、return "flying" を返してください。

class Duck(Swimmer, Flyer):class Goose(Flyer, Swimmer):pass だけで定義してください。

Duck().move()Goose().move() の結果を print し、親の並び順を入れ替えるだけで結果が変わる ことを確かめてください。

Python エディタ

コードを実行してください

ダイヤモンド継承と C3 線形化

もう一つよく出てくる形が ダイヤモンド継承 です。BC がそれぞれ A を継承していて、さらに DBC の両方を継承する — クラス図がひし形(ダイヤモンド)になる形のことです。

ダイヤモンド継承(ひし形)の構造
A(共通の親)BA を継承CA を継承DBとCを多重継承継承継承継承継承
このとき、D().method() を呼ぶと、A のメソッドは B と C より先に呼ばれる?それとも後? という疑問が生まれる

Python は C3 線形化(C3 linearization) というアルゴリズムで MRO を計算し、「派生先(B, C)を先に試して、最後に共通の親(A)を見にいく」 という賢い順序を作ってくれます。

class A:
    def method(self):
        return "A"

class B(A):
    def method(self):
        return "B"

class C(A):
    def method(self):
        return "C"

class D(B, C):       # ダイヤモンド
    pass

print(D().method())  # B

この例では D().method()"B" を返します。MRO の順序が D → B → C → A だからです。仮に Bmethod がなければ Cmethod が、それも無ければ Amethod が呼ばれます。「左から、しかし共通の祖先は最後」 がダイヤモンド継承の挙動の覚え方です。

クラス名で明示的に親メソッドを呼ぶ

多重継承していて、特定の親のメソッド を狙って呼びたいときは super() だと MRO の順で 1 つしか呼べません。そういうときは 親クラス名.method(self, ...) と書いて、明示的に呼び出します。

この書き方ではクラス側からメソッドを呼ぶ形になるため、第 1 引数に self を自分で渡す必要がある点が普通のメソッド呼び出しと違います。覚えておきたい唯一のクセです。

super() と クラス名指定の違い
C().hello()super().hello()MRO 順で1 つだけA.hello(self)B.hello(self)A も B も両方呼べる
super()MRO の上から 1 つ だけ呼ぶ。A.hello(self) のように クラス名で呼ぶ と、MRO に関係なく 狙ったクラスのメソッドだけ を実行できる。複数の親メソッドを順番に呼びたいときに使う。
class A:
    def hello(self):
        print("hello from A")

class B:
    def hello(self):
        print("hello from B")

class C(A, B):
    def hello(self):
        A.hello(self)        # A の hello を明示的に呼ぶ
        B.hello(self)        # B の hello も明示的に呼ぶ
        print("hello from C")

C().hello()
# hello from A
# hello from B
# hello from C

クラス名指定で 両方の親の hello を呼ぶ C を書きます。

class A: を定義し、hello(self)print("hello from A") を実行してください。

class B: を定義し、hello(self)print("hello from B") を実行してください。

class C(A, B): を定義し、hello(self) の中で A.hello(self)B.hello(self)print("hello from C") の順に呼んでください(self を自分で渡す のが大事)。

C().hello() を実行して 3 行表示されることを確認してください。

Python エディタ

コードを実行してください

多重継承は強力だが、使いどころは限定的

多重継承は便利な反面、MRO を意識し続けないと挙動が読めない という負担があります。実務では、無理に多重継承で解こうとせず、1 つの親 + 機能ごとに小さなクラスを属性として持たせる(合成) ほうが分かりやすくなる場合が多いです。多重継承を使うのは、Swimmer / Flyer のように独立した「能力」を組み合わせたい ような場面に限定するのが安全です。

やりたいこと書き方
複数の親を継承するclass 子(親A, 親B): ...
どの親が優先されるか確認クラス名.__mro__ (CPython)
MRO の上から 1 つだけ呼ぶsuper().method(...)
特定の親のメソッドを狙う親クラス名.method(self, ...)
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1次の宣言 class Duck(Swimmer, Flyer): で、両方に同名の move メソッドがあったとき、どちらが優先されますか?

Q2Python のクラスの メソッド解決順序(MRO) を確認するための書き方はどれですか?(CPython の場合)

Q3多重継承で 特定の親クラスのメソッドを名指しで呼ぶ 書き方として正しいのはどれですか?