📖

Pythonでclassを使う:番外編(1) 多重継承&ダイヤモンド継承

2023/04/01に公開

多重継承とダイヤモンド継承

多重継承とは、クラスが複数のクラスを継承することです。
多重継承の一つの例であるダイヤモンド継承とは、多重継承の一種であり、クラスAを継承したクラスB、クラスAを継承したクラスCが存在しているときに、クラスBとクラスCを継承することです。

ダイヤモンド継承は図にすると以下です。

多重継承の例

もっとも簡単な多重継承の書き方は以下です。

class A:
    def __init__(self):
        pass
class B:
    def __init__(self):
        pass
class C(A,B):
    def __init__(self):
        A.__init__(self)
        B.__init__(self)

ただし、classでの継承基底クラスの順番が必ずしも初期化順とは限らない(__init__の呼び出しの順が優先される)ため、注意が必要です。

ダイヤモンド継承

悪い例

ダイヤモンド継承は例えば以下のように書けます。

class A:
    def __init__(self):
        self.v = 1
class B(A):
    def __init__(self):
        A.__init__(self)
	self.v *= 2
class C(A):
    def __init__(self):
        A.__init__(self)
	self.v += 3
class D(B,C):
    def __init__(self):
        B.__init__(self)
        C.__init__(self)

class Bself.vを2倍、class Cself.vに3を足する処理を初期化で行います。
ここでclass Dclass Bclass Cの機能を継承したいのでself.vを2倍する機能とself.vに3を足す処理を行いたいです。

ですが現状、C.__init__(self)self.vを初期化してしまうプログラムになってしまっているので正しく多重継承できていません。

そこでclass Aself.vの初期化を一度のみ行うことが必要です。

良い例

何度も継承されるclass Aの初期化を一度のみ行うためにはsuperを用います。

superを用いた継承は以下です。

class B(A):
    def __init__(self):
        super().__init__()
        self.v *= 2
class C(A):
    def __init__(self):
        super().__init__()
        self.v += 3
class D(B,C):
    def __init__(self):
        super().__init__()

このとき、継承は全てsuper().__init__()で書けます。(今回は引数がないですが、必要に応じて入れる)

super().__init__()を用いることで、全てのクラスは1度のみ初期化され、継承の順番が(C3線型化によって)決定します。

初期化の順序

クラスはD->B->C->Aの順に初期化されます。実際の処理の順番はA->C->B->D

確認の仕方は2種類あって一つは今回の場合に限ってself.vの値を確認することです。

実際に以下のように実行すると8が出力されます。(1+3)*2という処理がされています。

print(D().v)

もう一つの方法は、継承クラスのmroを確認することです。
以下のようにして 初期化の呼び出される順番を確認できます。

print({i+1:v for i,v in enumerate(D.mro)})
{1: <class '__main__.D'>, 2: <class '__main__.B'>, 3: <class '__main__.C'>, 4: <class '__main__.A'>, 5: <class 'object'>}

また実際の初期化の作業の順序を確認したい時は、以下のようにします。

print({i+1:v for i,v in enumerate(D.mro()[::-1])})
{1: <class 'object'>, 2: <class '__main__.A'>, 3: <class '__main__.C'>, 4: <class '__main__.B'>, 5: <class '__main__.D'>}

Discussion