Python と mix-in

http://d.hatena.ne.jp/w_o/20081205#p3 ←のエントリは、タイトルと本文とTB先に情報が分散していて引用しにくいので、このエントリで書きたい話題をまとめると、

  1. Rubyはmix-inができるので、クラスの継承が必要無いので(親クラスのコンストラクタ呼び出しの書き方が)あまり関係無い。
  2. Pythonでmix-inをするには多重継承をしないといけないので、 super を使った方式で親クラスのコンストラクタを呼び出さないといけない。

まず、 mix-in をぶっちゃけると、「メソッドの固まりを取り込む」ことで良いよね?メソッドの固まりは、混ぜる先にある既存のメソッドに依存していいし、混ぜる先は混ぜるのに必要な前提条件を満たさなければならない。

class CmpMixin(object):
    """__lt__ と __eq__ を利用して、 __le__, __ge__, __gt__ を実装する mix-in class."""
    def __le__(self, other):
        return self.__eq__(other) or self.__lt__(other)
    def __ge__(self, other):
        return not self.__lt__(other)
    def __gt__(self, other):
        return not self.__le__(other)

class Foo(CmpMixin):
    def __init__(self, num):
        self.num = num
    def __lt__(self, other):
        return self.num < other.num
    def __eq__(self, other):
        return self.num == other.num

f1 = Foo(1)
f2 = Foo(2)

print f1 < f2, f1 <= f2, f1 > f2, f1 >= f2

まず、mix-in は継承の限定された形であり、mix-in ができるから継承がいらないということにはならない。

ただし、Rubyの親クラスのコンストラクタ呼び出し(というよりも親クラスの同名関数呼び出し)は、 id:w_o の考えるコンストラクタから親コンストラクタを呼び出す構文の評価基準を全て満たしている。(これはRubyのクラスはPythonのクラスに比べて多重継承が無い為などの背景があるためで、「だからRubyが優れている」と言うわけではない。PythonのポリシーよりRubyのポリシーの方が id:w_o のポリシーに近いということだろう。)

次に、mix-in class は object 以外を親に持つことが無い。なので、 super(ThisClass, self).method() の形の呼び出しは不要である。Rubyのmix-inとPythonのsuper()の構文を比較するのは少し筋が違うと思う。

このあたりは、Cooperative methods and "super" を読んだ後に、id:w_o は「Pythonのモダンな親クラスのコンストラクタ呼び出し方法は super(ThisClass, self).__init__() だ」という意識があって、 ParentClass.__init__(self) が一般的だと考えていた僕とすれ違いがあった。 Pythonにコンストラクタなんて構文は無いし、親クラスを呼び出す構文も無い - methaneの日記 で僕が「まず「自分のクラス名が必要」って意味が判らない。」と反応してしまったのもそれが原因。

"Cooperative methods and "super"" で "but perhaps also one of the most unusual features of the new classes" と書かれているように、標準ライブラリの中を見ても super はそれほど使われていない。この状況は、Py3k で変わると思われるが、それは次のエントリで。

最後に、これは余談になるが、PythonでもRubyとほぼ同じ形の "module を mix-in" ができる。

#CmpMixn.py
def __le__(self, other):
    return self.__eq__(other) or self.__lt__(other)
def __ge__(self, other):
    return not self.__lt__(other)
def __gt__(self, other):
    return not self.__le__(other)

#foo.py
class Foo(object):
    from CmpMixin import *
    def __init__(self, num):
        self.num = num
    def __lt__(self, other):
        return self.num < other.num
    def __eq__(self, other):
        return self.num == other.num

単にできるというだけであって、Warningが出るので勧められないけどね。

id:w_o は dictを回すとかいう答えは要らないと言ってたけど、dictに限らず関数の集合さえ用意すれば簡単にmix-inみたいな「クラスにメソッド・プロパティを追加する」ことができるのはPythonの特徴の一つ。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。