継承と多重継承とミックスイン

pythonは多重継承にも対応しており

多重継承には菱形継承問題が。
組込み関数superには菱形継承問題への対応が。

  • 菱形継承問題 - Wikipedia

    2.1 以前の Python では、多重継承に対し、深さ優先-左から右の順でクラスのリストを生成する。Python 2.2 で導入され Python 3 では統一された、新スタイルクラス[1]では、全てのクラスは共通の基底クラス object から派生させるため、菱形継承への対処が重要になった。この時に同時に導入された順序は 2.2 でのみの採用にとどまったためここでは説明しない[2]。Python 2.3 以降および Python 3 では C3(w:C3 linearization)が採用された

メソッド解決順序

多重継承の複雑さを回避する手段としてミックスインという手法がある。

  • Mix-in - お気楽 Python プログラミング入門

    これらの問題を回避するため、インスタンス変数 (属性) を継承するスーパークラスはひとつだけに限定して、あとのスーパークラスはメソッド (実装) だけを継承するという方法があります。この方法を Mix-in といいます。
    具体的には、インスタンス変数を定義せずにメソッドだけを記述したクラスを用意します。属性の継承は単一継承になりますが、実装のみを記述したクラスはいくつ継承してかまいません。

多重継承の機能をあえてフルには使わずに、スーパークラスの役割をするクラスを1つにしぼり、他のスーパクラスにはJavaやc#で言うインターフェースの役割をするクラスを複数継承するという事か。

ここからは、サンプルプログラムを。

継承の例。
子クラスから親クラスより継承したメソッドを実行

class SuperCls(object):
    def method(self):
        print u"スーパクラスのメソッドを実行しました。"

class SubCls(SuperCls):
    pass

sub_inst=SubCls()
sub_inst.method()

実行結果

スーパクラスのメソッドを実行しました。

多重継承の例
子クラスから親クラスAより継承したメソッドと親クラスBより継承したメソッドを実行

class SuperClsA(object):
    def method_a(self):
        print u"SuperClsAのメソッドを実行しました。"

class SuperClsB(object):
    def method_b(self):
        print u"SuperClsBのメソッドを実行しました。"

class SubCls(SuperClsA,SuperClsB):
    pass

sub_inst=SubCls()
sub_inst.method_a()
sub_inst.method_b()

実行結果

SuperClsAのメソッドを実行しました。
SuperClsBのメソッドを実行しました。

とここまでは、何の変哲も無いので順調に。

多重継承と組込み関数super

super - 親クラスのメソッドを呼び出す」にてsuperは多重継承に対応している旨のような事を述べた。

では、多重継承の場合に同名の親クラスのメソッドをsuperで呼び出す場合は?

class SuperClsA(object):
    def method(self):
        print u"SuperClsAのmethodを実行しました。"

class SuperClsB(object):
    def method(self):
        print u"SuperClsBのmethodを実行しました。"

class SubClsAB(SuperClsA,SuperClsB):
    def method(self):
        print u"SubClsABのmethodを実行しました。"
        super(SubClsAB,self).method()

sub_inst=SubClsAB()
sub_inst.method()

実行結果

SubClsABのmethodを実行しました。
SuperClsAのmethodを実行しました。

あれあれ?
SuperClsBのmethodは実行してくれないのか?
そうか、superは親クラスのリストを最初(左側)のクラスから順にメソッドを探していって最初に見つかったメソッドが実行されるのか。

では、SuperClsBのmethodも実行されるようにするには、と悩んだすえに以下の記事を見つけた。

これを参考にSubClsABのmethodのコードを修正。
組込み関数superを更に追加してsuperの第1引数にSuperClsAを指定して,SuperClsAの次からメソッドを検索してメソッドを実行させる。

class SubClsAB(SuperClsA,SuperClsB):
    def method(self):
        print u"SubClsABのmethodを実行しました。"
        super(SubClsAB,self).method()
        super(SuperClsA,self).method()

実行結果

SubClsABのmethodを実行しました。
SuperClsAのmethodを実行しました。
SuperClsBのmethodを実行しました。

SuperClsBのmethodも無事に実行された。

という事はSuperClsAとSuperClsBの継承順を逆にした場合には

class SubClsBA(SuperClsB,SuperClsA):
    def method(self):
        print u"SubClsABのmethodを実行しました。"
        super(SubClsBA,self).method()
        super(SuperClsB,self).method()

sub_inst=SubClsBA()
sub_inst.method()

実行結果

SubClsABのmethodを実行しました。
SuperClsBのmethodを実行しました。
SuperClsAのmethodを実行しました。

それならば、SuperClsAにメソッドが定義されていなくて、更にSuperClsAの親クラスにメソッドの定義があった場合は。

class SuperSuperClsA(object):
    def method(self):
        print u"SuperSuperClsAのmethodを実行しました。"

class SuperClsA(SuperSuperClsA):
    pass

class SuperClsB(object):
    def method(self):
        print u"SuperClsBのmethodを実行しました。"

class SubClsAB(SuperClsA,SuperClsB):
    def method(self):
        print u"SubClsABのmethodを実行しました。"
        super(SubClsAB,self).method()
        super(SuperSuperClsA,self).method()

sub_inst=SubClsAB()
sub_inst.method()

実行結果

SubClsABのmethodを実行しました。
SuperSuperClsAのmethodを実行しました。
SuperClsBのmethodを実行しました。

super(SubClsAB,self).method()は親クラスのリストを最初(左側)のクラスから順にメソッドを探していって無ければ更にその子クラスをたどっていてSuperSuperClsAのメソッドを見つける。
そして、SuperSuperClsAの次のメソッドを見つけるにはsuper(SuperSuperClsA,self).method()を実行するという事か。

このルールがわかれば自由にスーパクラスのメソッドを呼ぶ事ができるぞ。

でも、親クラスの検索順を気にするのが面倒でややこしいと思うなら、少しコードが長くなるが親クラスのメソッドから次の親クラスのメソッドを呼んであげるために、親クラスのメソッドすべてにtry文で囲ったsuperを入れてあげるのも1つの手。

これならワンパターンでコードが記述できる。
こちらの方がわかりやすいかな。

class SuperSuperClsA(object):
    def method(self):
        print u"SuperSuperClsAのmethodを実行しました。"
        try:
            super(SuperSuperClsA,self).method()
        except AttributeError:
            pass

class SuperClsA(SuperSuperClsA):
    pass

class SuperClsB(object):
    def method(self):
        print u"SuperClsBのmethodを実行しました。"
        try:
            super(SuperClsB,self).method()
        except AttributeError:
            pass

class SubClsAB(SuperClsA,SuperClsB):
    def method(self):
        print u"SubClsABのmethodを実行しました。"
        super(SubClsAB,self).method()

sub_inst=SubClsAB()
sub_inst.method()

実行結果

SubClsABのmethodを実行しました。
SuperSuperClsAのmethodを実行しました。
SuperClsBのmethodを実行しました。

何でtry文が必要かって?
試しにtry文をとってみればわかるのだが、親クラスにワンパターンでsuperを入れてやると最後はobjectクラスまでたどってメソッドを探しにいってそこでobjectクラスにメソッドが定義されていないとエラーになってしまうからサ。

ページのトップへ戻る