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

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

Pythonの多重継承をDuck/Gooseで解説。class子(親A, 親B): の書き方、親の並び順が結果を変えるMRO、ダイヤモンド継承のC3線形化、クラス名で特定の親メソッドを名指しする方法を確認できます。

前回で 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多重継承で特定の親クラスのメソッドを名指しで呼ぶ書き方として正しいのはどれですか?