Q1次の宣言 class Duck(Swimmer, Flyer): で、両方に同名の move メソッドがあったとき、どちらが優先されますか?
多重継承と MRO — 複数の親クラスを継承する
Python の多重継承を解説します。class 子(親A, 親B): で複数の親クラスを継承する書き方、メソッドの探索順序を決める MRO、__mro__ での確認方法までを図解で押さえます。
前回で 1 つの親クラスを継承する書き方とオーバーライド・super() までを整理しました。Python では 複数の親クラスを同時に継承する こともできます。これを 多重継承 と呼びます。今回は多重継承の書き方と、複数の親に同じ名前のメソッドがあるときに どの親が呼ばれるか を決めるルール — メソッド解決順序(MRO) — を見ていきます。
多重継承の基本構文
多重継承の書き方は単純で、親クラスをカンマ区切り で並べるだけです。class Duck(Animal, Swimmer, Flyer): のように書けば、Duck は Animal / 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 は Animal から 属性と __init__、Swimmer から swim、Flyer から fly をそれぞれ引き継ぐ。speak だけは Duck 自身でオーバーライド。メソッド解決順序(MRO) — どの親が優先されるか
問題は、複数の親に同じ名前のメソッド がある場合です。たとえば Swimmer と Flyer の両方に 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
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 が現れます。
ダイヤモンド継承と C3 線形化
もう一つよく出てくる形が ダイヤモンド継承 です。B と C がそれぞれ A を継承していて、さらに D が B と 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 だからです。仮に B に method がなければ C の method が、それも無ければ A の method が呼ばれます。「左から、しかし共通の祖先は最後」 がダイヤモンド継承の挙動の覚え方です。
クラス名で明示的に親メソッドを呼ぶ
多重継承していて、特定の親のメソッド を狙って呼びたいときは super() だと MRO の順で 1 つしか呼べません。そういうときは 親クラス名.method(self, ...) と書いて、明示的に呼び出します。
この書き方ではクラス側からメソッドを呼ぶ形になるため、第 1 引数に self を自分で渡す必要がある点が普通のメソッド呼び出しと違います。覚えておきたい唯一のクセです。
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
多重継承は強力だが、使いどころは限定的
多重継承は便利な反面、MRO を意識し続けないと挙動が読めない という負担があります。実務では、無理に多重継承で解こうとせず、1 つの親 + 機能ごとに小さなクラスを属性として持たせる(合成) ほうが分かりやすくなる場合が多いです。多重継承を使うのは、Swimmer / Flyer のように独立した「能力」を組み合わせたい ような場面に限定するのが安全です。
| やりたいこと | 書き方 |
|---|---|
| 複数の親を継承する | class 子(親A, 親B): ... |
| どの親が優先されるか確認 | クラス名.__mro__ (CPython) |
| MRO の上から 1 つだけ呼ぶ | super().method(...) |
| 特定の親のメソッドを狙う | 親クラス名.method(self, ...) |
理解度チェック
まずは1問ずつ答えてみましょう。
Q2Python のクラスの メソッド解決順序(MRO) を確認するための書き方はどれですか?(CPython の場合)
Q3多重継承で 特定の親クラスのメソッドを名指しで呼ぶ 書き方として正しいのはどれですか?