<一覧に戻る

多重継承(Python)

「このクラスに、あの機能もこの機能もまとめて持たせたい。どう書けばいいの?」そんなときに役立つのが、多重継承です。

多重継承は、1つのクラスが複数の親クラスから属性やメソッドを受け継げる、Pythonのオブジェクト指向の強力な仕組みです。

便利な一方で、使い方を間違えると「どのメソッドが呼ばれているの?」と混乱しがち。はじめての方にもわかりやすく、基本から注意点まで丁寧に解説します。

多重継承の基本:書き方と考え方

Pythonでは、クラス定義の丸括弧の中に親クラスをカンマで並べるだけで、多重継承ができます。 左に書いた親ほど優先される、というのが基本のルールです。

以下のサンプルコードを見てみましょう。

class ClassA:
    def method_a(self):
        return "Method from ClassA"

class ClassB:
    def method_b(self):
        return "Method from ClassB"

class ClassC(ClassA, ClassB):
    pass

c = ClassC()
print(c.method_a())  # => Method from ClassA
print(c.method_b())  # => Method from ClassB

ClassCはClassAとClassBの両方を「親」に持つため、method_aとmethod_bをそのまま使えます。 クラスを部品のように組み合わせられる、とイメージすると理解しやすいでしょう。

アヒルは「動物」でもあり「鳥」でもある

「アヒルは鳴く(動物の特徴)し、飛ぶ(鳥の特徴)こともある」。 この自然な発想をコードで表すと、多重継承がしっくりきます。

これを実装したのが、以下のコードです。

# 親クラス1
class Animal:
    def speak(self):
        return "Animal speaks"

# 親クラス2
class Bird:
    def fly(self):
        return "Bird flies"

# 子クラス(多重継承)
class Duck(Animal, Bird):
    def quack(self):
        return "Duck quacks"

duck = Duck()
print(duck.speak())  # => Animal speaks
print(duck.fly())    # => Bird flies
print(duck.quack())  # => Duck quacks

DuckはAnimalとBirdの両方を継承しているので、鳴く(speak)と飛ぶ(fly)の両方ができます。 さらにDuckだけの機能としてquack(ガーガー鳴く)も持たせています。

1つのインスタンスに複数の「役割」を合体できるのが、多重継承の気持ちよいところです。

名前が重なったらどうなる?メソッド解決順序(MRO)

「もし親クラスどうしが同じ名前のメソッドを持っていたら、どっちが呼ばれるの?」という疑問は当然わきます。

PythonはMRO(Method Resolution Order:メソッド解決順序)という仕組みで、呼ぶ順番を厳密に決めています。

基本は「左から右へ」。次の例を見てください。

class A:
    def hello(self):
        return "hello from A"

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

class C(A, B):
    pass

c = C()
print(c.hello())      # => hello from A(左側のAが優先)
print(C.mro())        # MRO(検索順)を確認してみる
# 例)[<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

CはA, Bの順で継承しているため、同名メソッドhelloはAが優先されます。 動作があいまいにならないよう、Pythonはこの順番(MRO)を常に意識してメソッドを探します。

親クラスの並び順は「振る舞いの優先度」を決めるスイッチだと覚えておくと安全です。

ダイヤモンド問題とsuper():同じ祖先を持つときのコツ

多重継承の「つまずきポイント」として有名なのが、ダイヤモンド問題です。 複数の親が同じ祖先クラスを共有していると、祖先のメソッドが二重に呼ばれてしまうことがあります。

これを避ける鍵がsuper()です。

各クラスが「協調的な継承」を行うよう、super()を使って次のクラスへバトンを渡します。

class LivingThing:
    def info(self):
        print("LivingThing")

class Animal(LivingThing):
    def info(self):
        print("Animal")
        super().info()  # 次のクラスに委ねる

class Bird(LivingThing):
    def info(self):
        print("Bird")
        super().info()  # 次のクラスに委ねる

class Duck(Animal, Bird):
    pass

d = Duck()
d.info()
# 出力(1回だけ通るのがポイント)
# Animal
# Bird
# LivingThing

super()を使うと、MROの順序に沿って「次に処理すべきクラス」にきれいに処理が渡ります。ダイヤモンド構造でも祖先が重複して呼ばれないため、想定通りに動きます。「多重継承でメソッドをオーバーライドするならsuper()」は実務でも強いルールです。super()の詳しい使い方は、同じ連載の「super() 関数の使い方」セクションも合わせて確認してください。

多重継承のメリットとデメリット

ここで、多重継承のメリットとデメリットをまとめます。

【メリット】 ・コードの再利用性が高まり、同じ機能を何度も書かずに済みます。 ・機能の組み合わせが柔軟になり、クラス設計の表現力が広がります。

【デメリット】 ・クラスの関係が複雑になりやすく、読み手が把握しにくくなります。 ・同名メソッドの衝突や、MROの理解不足による予期せぬ挙動が起きやすくなります。

「本当に多重継承が必要?」と一度立ち止まる姿勢が、読みやすいコードへの第一歩です。

実務でのコツ:ミックスイン(Mixin)として使う

実務では、多重継承は「ミックスイン」と呼ばれる小さな機能の塊を組み合わせる目的で使うことが多いです。ミックスインは状態(属性)をほとんど持たず、メソッドだけを提供する軽量の親クラスです。

class FlyMixin:
    def fly(self):
        return "I can fly"

class QuackMixin:
    def quack(self):
        return "quack!"

class Animal:
    def speak(self):
        return "some sound"

class Duck(Animal, FlyMixin, QuackMixin):
    pass

d = Duck()
print(d.speak())  # => some sound
print(d.fly())    # => I can fly
print(d.quack())  # => quack!

「〜できる(-able)」のような名前や「〜Mixin」というクラス名にすると、読む人に意図が伝わりやすくなります。共通機能を追加するだけなら、ミックスインがとても相性のよい選択肢です。

まとめ:多重継承は「強力だけど丁寧に」

多重継承は、Pythonで機能を組み合わせるための強力な道具です。うまく使えば、重複を減らし、読みやすさも保てます。ポイントは次のとおりです。

  • 親クラスの順序が優先度を決める。迷ったらmro()で確認。
  • オーバーライドではsuper()を使い、協調的に処理を渡す。
  • 状態を持たない小さな機能はミックスインにまとめる。
  • 必要以上に複雑にしない。場合によっては合成(コンポジション)も検討する。

「本当にここは多重継承が最適解?」と問いかけながら設計することで、シンプルで強いコードに近づけます。次のステップとして、関連トピックの「メソッド解決順序(MRO)」「super() 関数の使い方」「ポリモーフィズム」も合わせて学ぶと理解が一気に深まります。

出力結果: