銀月の符号

Python 使い見習いの日記・雑記

メタクラスの世界へようこそ! 前編

http://d.hatena.ne.jp/rockRicefield/20090917/1253165583」を受けて。北の地の元ファゴット吹き、現 Python 使い見習いの fgshun 、メタクラスについて語ってみます。つっこみは大歓迎です。

クラスファクトリ type は関数ではなくてクラス

type 関数を使うことでクラスを作ることができる、という内容が Python ライブラリリファレンスの『組み込み関数』に書いてあります。

class Spam(object):
    pass

Spam = type('Spam', (object,) {})

です。コレを読んで、 type はクラスオブジェクトを作る関数なんだ、という解釈をすると、先日に紹介した次の関数が導き出せます。

def class_factory(name):
    return type(name, (object,), {})

type とインターフェースをそろえれば、関数でも立派にメタクラスの役割を担うことができます。こうすると、クラス属性 n=7 を持ったクラスを作るメタクラス M の出来上がりです。

def M(name, bases, dict_):
    dict_['n'] = 7
    return type(name, bases, dict_)

でも、これだと type の一側面にしか触れていません。実は関数ではないんです。type はクラスなのです! つまり、さきほどの type 呼び出しを分解するとこういうことになります。

Spam = type.__new__(type, 'Spam', (object,), {})
if isinstance(Spam, type): # ちなみに、これは True です
    Spam.__init__('Spam', (object,), {})

すると、 dict_['n'] = 7 のような処理の追加もオブジェクト指向でやってみたい、という欲求に駆られることになります(ホントか?

name = 'Spam'
bases = (object,)
dict_ = {}
# こことか
Spam = type.__new__(type, name, bases, dict_)
# こことか
Spam.__init__(name, bases, dict_)
# ここに処理を入れたい

ようこそ

カスタムメタクラスの世界へようこそ! この世界の扉を開けてしまった以上、後戻りはできません(まて

type はクラスなので継承してサブクラスを作ることができます。と、いうわけで、 __new__ メソッドや __init__ メソッドをオーバーライドしたサブクラスを作って type の代わりに使えば、クラス生成時に暗黙の前後処理を走らせることができるようになります。

class Meta(type):
    def __new__(mcls, name, bases, dict_):
        # ここで前処理を追加、 name, bases, dict_ を改変とか
        cls = super(Meta, mcls).__new__(mcls, name, bases, dict_)
        # ここにも中間処理が入れられる
        return cls

    def __init__(cls, name, bases, dict_):
        # ここで中間処理を追加
        super(Meta, cls).__init__(name, bases, dict_)
        # ここで後処理を追加、 cls.n =7 のように属性追加するとか

type.__new__ はよくわかっていないのですが*1、第1引数のクラスのインスタンスを返します。つまり super(Meta, mcls).__new__(mcls, name, bases, dict_)
は mcls 、つまり Meta のインスタンスになります*2Python の言語仕様には「__new__ は関係ないクラスのインスタンスを返してもよい、その場合 __init__ をショートカットする」というものがありますが、この Meta.__new__ は素直に Meta のインスタンスを返すことになるので一安心です。*3。 __new__ レベルの黒魔術はつかわれていないです。

後編(2009/9/11 現在、作成中)に続く、かも。終わったかも。

*1:実装はたぶん Objects/typeobject.c にある。当然、 C 言語製。 __new__ の役割はメモリの確保、かな。

*2:mcls は必ず Meta になるわけではない。 Meta のサブクラス内でこの __new__ が呼ばれると mcls に別のものが入ることになる。

*3:今のところは。 cls = super(Meta, mcls).__new__(mcls, name, bases, dict_) と return cls の間に処理を入れたり、削除してスーパークラスの __new__ を呼ばないようにした場合などは前提が崩れることも。かなり黒い。