super(self.__class__, self) は危険 (Python 2.x)

Python2.xのsuperのお話です*1

まさかの今の今まで知らなかったので戒めとして記録しておきます。

結論

結論から言えば「2回以上継承されうるクラスでは*2、super関数で親クラスを参照する場合は、super(self.__class__, self)ではなく、ちゃんとsuper(クラス名, self)と表記しなければならない。」ということです。

そもそもsuper関数とは?

super関数は、ドキュメントによればメソッドの呼び出しを type の親または兄弟クラスに委譲する、プロキシオブジェクトを返す関数です。イメージとしては、子クラスのインスタンスがbindされている親クラスのインスタンス的な感じです*3。子クラスから親クラスの同名メソッドを呼び出す時に使われます。

ただ、実際の所、Pythonではsuper関数がなくても親クラスのメソッドを呼べたりします。

class A(object):
    def print_class(self):
        print "A:", self.__class__

class B(A):
    def print_class(self):
        print "B:", self.__class__
        A.print_class(self)

class C(B):
    def print_class(self):
        print "C:", self.__class__
        B.print_class(self)

if __name__ == "__main__":
    A().print_class()
    B().print_class()
    C().print_class()

実行結果

A: <class '__main__.A'>
B: <class '__main__.B'>
A: <class '__main__.B'>
C: <class '__main__.C'>
B: <class '__main__.C'>
A: <class '__main__.C'>

何故これで良いのかというと、Pythonのメソッドにはboundとunboudの2種類があるからです。boundなメソッド*4は、インスタンスが第一引数selfにbindされているメソッドのことですので、適当な変数に代入してもbindされた状態でメソッド呼び出しが出来たりします。最近は、JavaScriptでもthisをbindするのが、しばしば使われるテクニックになっていると思いますが、それと要領は同じです。これ以上の詳細はここでは省略します。

話を戻すと、だったらsuper要らないんじゃない?となりそうなところですが、superにはsuperの良さがあります。それが、多重継承のいわゆるダイヤモンド継承の時に、(中略)なのですが、多重継承が許されるのはsh(ryなので*5、詳細は記事末尾の参考文献を見てください*6

super(self.__class__, self) とは?

Python2.xにおいて、子クラスから親クラスのメソッドを呼び出す際にしばしば使われるイディオムです。このイディオムは、self.__class__を使うことで、親クラスを明示的に指定する必要がなくなるので、クラス名を変えても、全てのsuper部分を修正する必要がないのが便利ということで使われるようになったのだと思います。

super(self.__class__, self) が危険な理由

self.__class__が何を指すのかがポイントです。

# -*- coding: utf-8 -*-
# 出力がえらいこっちゃになるので最大再帰回数を変更
import sys
sys.setrecursionlimit(5) 

class A(object):
    def print_class(self):
        print self.__class__

class B(A):
    def print_class(self):
        super(self.__class__, self).print_class()

class C(B):
    def print_class(self):
        super(self.__class__, self).print_class()
    
if __name__ == "__main__":
    B().print_class() # <class '__main__.B'>
    C().print_class() # infinite recursion!

子クラスでsuper(self.__class__, self)した時、親クラスのメソッドでのself.__class__は子クラスのままなので、擬似コードで書けば以下の様になります。

class A(object):
    def print_class(self):
        print self.__class__

class B(A):
    def print_class(self):
        super(C, self).print_class() # = B.print_class(self)

class C(B):
    def print_class(self):
        super(self.__class__, self).print_class()
    
if __name__ == "__main__":
    C().print_class() # infinite recursion!

ということで、注意しましょう!おしまい。

*1:Python3.xではsuper()だけで親クラスが取れるのでこの問題は起きない。

*2:別の言い方をすれば、「末端のクラス以外では」。Javaのfinalクラスのようなことがしたい時には、逆にこの性質を利用する手もあるのかもしれない…いやないか。

*3:余計わからない?

*4:通常、インスタンス経由で呼び出すメソッドがbound method。

*5:冗談です。OOPのプロの方ゴメンナサイ。

*6:人はこれを手抜きと呼ぶ