2008年02月21日

メタクラスで遊ぶ(1)

 今の世の中、オブジェクト指向プログラミングは当たり前だ。
 しかし、じゃ「メタクラスは?」と聞かれて即答できる人は、ある程度の勉強家だろう。
 なぜか……昨今の「オブジェクト指向プログラミング言語」の主流格であるC++にもJavaにも、メタクラスは存在しないからだ。

 無論、Smalltalkerなら「当然」なので、鼻で笑われるだろう。
 なんせ、クラスを作るには、メタクラスのインスタンスを作らなければならないからだ。
 つまり、メタクラスとは、クラスオブジェクトのクラス、すなわち「クラスのクラス」だ。
 正式には、あるオブジェクトがあると、そのクラスのクラスがメタクラス、ということになる。
 クラス自身がオブジェクトである言語でなければ、メタクラスが存在するわけない、という意味はご了解できただろうか。
 Perl風Smalltalkを標榜するRubyには、メタクラスは当然ある。
 では、我らがPythonには? …… 実はメタクラスプロトコル(メタクラスを弄る仕組み)がちゃっかりあったりする。
 メタクラスは、普段は隠れているので、意識せずともプログラムは全然問題なく書ける。
 昨今結構沢山出た日本語のPython入門書にも、メタクラスへの記述は一切無い。
 では、メタクラスは、知らなくてもいいことなのか?
 ありていに言えば「知らんでよい」と思われる。
 「知らんでよい」のだが、それだったら例えばλ(lambda)文だって一切使わずともプログラムは書ける。
 メタクラスも似たようなモノである。
 というか、λもメタクラスも、濫用するとかえってコードが汚染されて良くないモノである。
 事実「美しいコード」自慢のPythonでは、関数型プログラミングの切り札のλも、オブジェクト指向プログラミングの切り札のメタクラスも、同様に煙たがられている。
 まぁ、プロの方で素人の私の駄文を参考にしてコーディングされる方は皆無だと思うので安心だが、念のため「メタクラスを仕事に使いたければ、ちゃんと他所で勉強してください」と言っておく。
 基本的に私のPythonコーディングは九割が「悪戯」なのだから。


■メタクラスを使ってクラスを作る

 λを使えば式の中で関数が生成できるのと同様、メタクラスプロトコルを用いれば、式の中でクラスを生成できる。
>>> X = type('X',(),{})
>>> x = X()
>>> X
<class '__main__.X'>
>>> x
<__main__.X object at 0x00C171F0>
 メタクラスを弄るには、typeオブジェクトを使用する。
 通常はオブジェクトタイプを判別するために使用するこのオブジェクトを関数的に利用するのだが、コレは実は「原始的メタクラス」でもある。
 メタクラスなので、クラスを生成することも可能なら、typeを継承することで、カスタムメタクラスを作ることも出来る。
 上の例は、クラスジェネレータとしてtypeを使った例である。
 使い方は以下の通り
	type('クラス名',(スーパークラスのタプル),{属性の辞書})
 属性は即ち、フィールド(スロット)やメソッドである。
 フィールド、メソッド付きのクラスも作ってみようか……(print関数が使えない2.xなら、sys.stdout.writeを使う?)
>>> C = type('C',(),{'x':666, 'y':lambda self:print('Hello')})
>>> c = C()
>>> c.x
666
>>> c.y()
Hello
>>>
 もう、勘のいい人は気づいたと思うが、グローバル空間に名前を残さずに、無名関数ならぬ無名クラスを作成することも可能である。
 そして、クラスを隠蔽したままオブジェクトを生成するという芸当も可能になる。
>>> n = type('',(),{})()
>>> n
<__main__. object at 0x00C171F0>
>>>
 コンテナとしてしか使わないオブジェクトにわざわざクラスを作るのが面倒、という人には便利かもしれない(空白カッコの行列が不気味、という方にはお勧めしかねるが)



■カスタムメタクラスを作る

 クラスを生成するメタクラスを作るには、typeオブジェクトを継承すれば良い。
 そして、クラスにメタクラスを適用するには、2.xまでなら、クラスの中で、__metaclass__属性にメタクラスを指定する。
>>> class M(type):pass
...
>>> class oldC:
...   __metaclass__ = M
...
 Python 3000からは、以下のように書ける。
>>> class newC(metaclass = M):
...   pass
...
 とりあえず、簡単な例として、型判別した時にclassオブジェクトでなく、オリジナルの表示ができるよう、__repr__属性を変更してみよう。

 その前に、__repr__属性の話を少しだけ。
 オブジェクトの文字表現は、オブジェクトのクラスによって決定されている。
 例えば整数値はリテラルそのままだし、文字列は(クォートがネストされていない限り)シングルクォートで囲まれて表示される。
>>> 666
666
>>> 'Hello'
'Hello'
>>> class C0:pass
...
>>> c0 = C0()
>>> c0
<__main__.C0 object at 0x00C269D0>
>>>
 この表現方法は、そのオブジェクトのクラスの__repr__属性によって決定される。
>>> class C1:
...   def __repr__(self):
...     return "C1's Object!"
...
>>> c1 = C1()
>>> c1
C1's Object!
>>>
 オブジェクトの文字表現はコレで十分なのだが、typeで「型(クラス)」を表示させると、以下のようになってしまう。
>>> type(666)
<type 'int'>
>>> type('Hello')
<type 'str'>
>>> type(c0)
<class '__main__.C0'>
>>> type(c1)
<class '__main__.C1'>
 c0もc1も「クラスオブジェクト」として、ひっくくられてしまう。
 コレでは格好悪いと思っても、クラスレベルでは表示は変更できない。
 というわけで、メタクラスをイジってみよう、というわけだ。
>>> class M(type):
...   def __repr__(cls):
...     return "<type '" + cls.__name__ + "'>"
...
>>> class C2(metaclass = M):
...   __name__ = 'C2'
...   def __repr__(self):
...     return "C2's Object!"
...
>>> c2 = C2()
>>> c2
C2's Object!
>>> type(c2)
<type 'C2'>
>>>
 Pythonはオブジェクト自身の名前を参照するのが異様に困難(かつ、その方法は不確定)なので、__name__という名札をつけてやる必要があるが、とりあえずコレで型を調べるとクラスの名前が表示されるようにはなる。
 本日はこの程度で……

この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/kikwai/51274308
この記事へのコメント
<追記>
 ここのページがシンプルにPythonのメタクラスを紹介してました。

 孫請プログラミング
 http://hehe.s9.xrea.com/
Posted by 機械伯爵 at 2008年02月22日 10:32
<追記 その2>

 あまり当然だから云い忘れたけど、基本的に「適当なオブジェクト」を作るなら、普通はこうする。

o = object()

 typeを使った例は「型を重複せずに」という但し書きが付いた場合の例である。
 でも、Singletonという訳でもないので、まぁ、実際、悪戯以外にはあまり使えない(苦笑)
Posted by 機械伯爵 at 2008年02月24日 14:57
<追記 その3>
 今試してみたら、objectクラスのインスタンスは、追加スロットを認めてくれないようだ。使ったことないから、気にしたこと無かったなぁ……

 とすれば、type('',(),{})()にも、意味が無いわけではないのか……
Posted by 機械伯爵 at 2008年02月27日 10:27