Q1次の宣言class Duck(Swimmer, Flyer):で、両方に同名のmoveメソッドがあったとき、どちらが優先されますか?
多重継承と MRO — 複数の親クラスを継承する
Pythonの多重継承をDuck/Gooseで解説。class子(親A, 親B): の書き方、親の並び順が結果を変えるMRO、ダイヤモンド継承のC3線形化、クラス名で特定の親メソッドを名指しする方法を確認できます。
前回で 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多重継承で特定の親クラスのメソッドを名指しで呼ぶ書き方として正しいのはどれですか?