このページで解説している内容は、以下の YouTube 動画の解説で見ることができます。

【Python入門】複数のクラスからの多重継承

複数のクラスからの多重継承

 Pythonでは、1つのクラスから継承するだけでなく、複数のクラスを同時に継承する多重継承を行うことも可能です。多重継承を使うと、それぞれの基底クラスがもつ機能や属性を1つの派生クラスに集約し、強力なクラス設計を実現できます。ここでは、多重継承の仕組みや設計上の注意点、そしてメソッド解決順序(MRO)について解説します。

プログラムのダウンロード

 「ダウンロード」から、JupyterLab で実行できるサンプルプログラムがダウンロードできます。ファイルは、ESET Endpoint Securityでウイルスチェックをしておりますが、ダウンロードとプログラムの実行は自己責任でお願いいたします。

1.多重継承の概要

1.1. 複数クラスの機能を同時に受け継ぐ

 通常の単一継承では、class 派生クラス名(基底クラス名): のように1つの基底クラスを継承します。これに対し、多重継承では class 派生クラス名(基底クラス名1, 基底クラス名2, ...): と書くことで、複数のクラスから機能を継承します。たとえば、ある基底クラスAから「Aの機能」を継承し、別の基底クラスBから「Bの機能」を継承することで、複合的なクラスを作ることができます。

1.2. 菱形継承(ダイヤモンド継承)問題

 多重継承では、継承元の基底クラスが部分的に重複してしまう「菱形継承」が起こる場合があります。これにより、どのクラスのメソッドを優先して呼び出すかなど、動作が不明瞭になる可能性があります。Pythonでは、この問題をMRO(Method Resolution Order)という仕組みで解決しています。

2.具体例:多重継承で洗濯と乾燥を両立するクラスを設計する

 ここでは、家電製品を表す Appliance クラス(基底クラス)から、Washer クラス(洗濯機)と Dryer クラス(乾燥機)を派生させ、それらを多重継承して「洗濯と乾燥の両方ができる」クラス WasherDryer を作ってみます。

# 基底クラス: Appliance
class Appliance:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def show(self):
        print("Name:", self.name)
        print("Price:", self.price)

# 派生クラス1: Washer(洗濯機)
class Washer(Appliance):
    def __init__(self, capacity, **kwargs):
        super().__init__(**kwargs)
        self.capacity = capacity  # 容量

    def show(self):
        super().show()
        print("Capacity:", self.capacity)

    def wash(self):
        print("Washing laundry with", self.name)

# 派生クラス2: Dryer(乾燥機)
class Dryer(Appliance):
    def __init__(self, energy_rating, **kwargs):
        super().__init__(**kwargs)
        self.energy_rating = energy_rating  # 省エネ性能など

    def show(self):
        super().show()
        print("Energy Rating:", self.energy_rating)

    def dry(self):
        print("Drying laundry with", self.name)

# 多重継承: WasherDryer(洗濯乾燥機)
class WasherDryer(Washer, Dryer):
    def __init__(self, name, price, capacity, energy_rating):
        # __init__をオーバーライド
        # ※多重継承でも、ここではsuper()の呼び出し順に注意
        self.name = name
        self.price = price
        self.capacity = capacity
        self.energy_rating = energy_rating

    def show(self):
        # 継承元のshowを順次呼び出し
        super().show()  # MROに従ってWasher→Dryer→Applianceへと辿る

# WasherDryerを実際に使ってみる
wd = WasherDryer(name="All-in-One WD", price=90000, capacity="8kg", energy_rating="A++")
wd.show()
wd.wash()
wd.dry()

実行結果

Name: All-in-One WD
Price: 90000
Energy Rating: A++
Capacity: 8kg
Washing laundry with All-in-One WD
Drying laundry with All-in-One WD

詳しい解説

  • Applianceクラス(基底クラス)
    ・製品名(name)と価格(price)を共通の属性としてもつ。
    show メソッドで名前と価格を表示する。
  • Washerクラス(派生クラス)
    Appliance を継承し、洗濯機としての機能を拡張。
    wash メソッドを追加し、洗濯動作を表現。
    show メソッドをオーバーライドして、容量(capacity)を表示。
  • Dryerクラス(派生クラス)
    Appliance を継承し、乾燥機としての機能を拡張。
    dry メソッドを追加し、乾燥動作を表現。
    show メソッドをオーバーライドして、省エネ性能などを示す energy_rating を表示。
  • WasherDryerクラス(多重継承クラス)
    class WasherDryer(Washer, Dryer): と書くことで、両方の機能を継承。
    ・このオブジェクトは washdry を同時に利用可能。
    show メソッドは super().show() を呼び出すと、MROの順序に従って WasherDryerAppliance の順でメソッドを探し、順次表示処理を行う。

3.多重継承とMRO

3.1. 菱形継承とメソッド解決順序

 もし WasherDryer の両方がさらに同じクラスを継承している場合、WasherDryer がどのタイミングでどちらのクラスのメソッドを呼ぶのかが不明瞭になることがあります。これがいわゆる「菱形継承問題」と呼ばれるものです。Pythonでは、この問題を解消するために、クラスごとに MRO(Method Resolution Order) を定め、呼び出すメソッドを順番に決定します。

3.2. __mro__ 属性の確認

 クラスオブジェクトに対して WasherDryer.__mro__ とアクセスすると、メソッド解決順序をタプルの形で確認できます。たとえば上記例では以下のように並ぶでしょう。

print(WasherDryer.__mro__)

実行結果

(<class '__main__.WasherDryer'>,
 <class '__main__.Washer'>,
 <class '__main__.Dryer'>,
 <class '__main__.Appliance'>,
 <class 'object'>)

この順序に従い、super().show() が順次探されて呼ばれます。

4.継承した__init__を活かす方法

 例では WasherDryer 内で __init__ の処理をすべて自前で書いていますが、Pythonでは、**kwargsを使い、必要な属性だけ取り出して残りをsuper().__init__ に回すという方法もよく使われます。そうすることで、基底クラスの初期化処理をうまく再利用でき、コードを短く保つことができます。

まとめ

  • 多重継承を使うことで、複数のクラスがもつ機能をひとつの派生クラスに集約できる。
  • ダイヤモンド(菱形)構造になると複数ルートから同じクラスを継承する場合があり、どのメソッドを使うのかが分かりづらくなる。
  • Pythonでは、MRO(メソッド解決順序) の仕組みによって、クラス継承先を一定の優先度で自動的に解決する。
  • super() の呼び出し順がMROによって変わるため、コードを理解するときは __mro__ を確認すると分かりやすい。

 多重継承は単一継承よりも複雑になる反面、うまく設計すれば非常に強力な再利用性を発揮します。実際の開発では、継承関係が分かりにくくなりすぎないよう注意しながら、必要に応じて使いこなすことが大切です。