Python の メタプログラミング (__metaclass__, メタクラス) を理解する

Pythonのメタプログラミング (__metaclass__) は組み込み関数 type の普段利用しない隠れた機能や、 普通は利用しない特殊メソッド __new__ などを理解する必要があり 理解するのが結構難しい。 あまり関連情報がまとまってるドキュメントがなくて理解するのに苦労したので情報をまとめておきました。

目次

事前知識

Customizing class creation (日本語:クラス生成をカスタマイズする) を読むと、型を取得するのに普通利用するbuiltin関数 type を継承していたり、 普通利用することのない __new__ が定義されていたりして、 type の隠された機能と __new__ について理解していないと 何が書かれているかさっぱり分からないと思います。 まずは __metaclass__ を理解する上で重要なこの2つについて整理しておきましょう。

type とクラス定義のあまり知られていない関係

ご存知のように Python には type という builtin 関数が定義されています。 type は type(obj) のように1つの引数を与えて obj の型を取得するのに利用したことがあるはずです。 普通はこちらの機能しか使いません。

しかし実は type にはもう一つの隠れた機能があります。 第1引数に文字列でクラス名、第2引数に親クラスの列、第3引数にクラスのメソッドや属性を定義した dict を渡して type を呼び出すとクラスを動的に定義することが可能です。

class ClassName(P0, P1):
  attrivute1 = value1

  def function1(self, args):
    ...

のように普段クラスを定義していると思いますが、これは type を使うと:

def function1(self, args):
   ...

ClassName = type('ClassName',
                 [P0,P1,],
                 {'attribute1': value1,
                  'function1': function1,})

のように定義することも可能です。この2つの定義は全く同じ実行結果が得られます。 ここで2つ目の type を使った定義をよく見てみると、 実は ClassName は type クラスにクラス名、親クラス、クラス定義を渡してインスタンス生成 したものだということが分かります。クラスは type のインスタンス なのです。 その証拠に type(cls)type を返しますし、 isinstance(cls, type) は True を返します。

特殊メソッド __new__

Python でクラスを定義する際、インスタンスを初期化するメソッドとして普通は __init__ を定義します。 厳密には Python には「コンストラクタ」という用語はありませんが、 C++/Java におけるコンストラクタに相当する処理は普通 __init__ に書かれます。 しかし実は Python にはインスタンスの生成方法を定義するもう一つの 特殊メソッド __new__ が存在します (__new__ の日本語のドキュメント)。

Python でクラスを定義して、それを呼び出してインスタンスの生成を行うと __init__ が 暗黙的に呼び出されインスタンスの初期化が行われると理解していると思いますが、 実はその前に __new__ による処理が存在しています。 クラスのインスタンス生成を行った際に暗黙的に行われている処理はより正確に書くと

  • class ClassNameClassName() によってインスタンス生成された場合
  • まず ClassName.__new__ が第1引数に ClassName, 残りの引数に ClassName に与えた残りの引数が与えられて呼び出される。
  • 普通は __new__ は定義されていないので親クラスをたどっていって最終的に object.__new__ が呼び出される。object.__new__ は第1引数で与えられた クラスのインスタンスを生成して返す。object.__new__ が返すインスタンスは __init__ が実行される前の未初期化のインスタンスである点に注意。
  • __new__ が ClassName のインスタンスを返した場合に限り ClassName.__init__ が呼び出される。

というようになっています。 例えばものすごく極端な例ですが:

class C(object):
  def __new__(cls, arg):
    return str(arg)

  def __init__(self):
    print '__init__'

c = C(43)
print type(c)

のようなコードを書くと、c には '43' が代入されます。 そして __init__ は呼び出されません。

__new__ の役割はかなり理解しづらいと思うので よくドキュメントを読んでサンプルを書いて動かしていろいろ試してみたほうがよいかと思います。 個人的には ClassNameを呼び出すと __init__ が暗黙的に呼び出されるという 先入観が強すぎるせいか、 __new__ で何が制御できるのか理解するのがなかなか大変でした。

__metaclass__

class を定義すると自動的に type('ClassName', ...) が呼び出されてクラスが生成されるということを type の節で述べました。 実は Python ではこの class を定義される際に暗黙的に呼び出される関数を別の関数で置き換えることができます。 これが __metaclass__ です (__metaclass__ の日本語のドキュメント)。

classの定義に __metaclass__ が存在するとクラスを生成する際に __metaclass__ に格納された関数が type の代わりに呼び出されます。 metaclass という名前がついていますが、__metaclass__ は class である必要はありません。 type と同様の引数を受け取れる callable なオブジェクトならば何でも __metaclass__ として利用できます。 例えば

def tolower_classname(name, bases, dict):
  return type(name.lower(), bases, dict)

class ClassName(object):
  __metaclass__ = tolower_classname

  print ClassName.__name__  # classname

のようなコードを書くと、ClassName の名前が 'classname' になります (classname というクラスが ClassName という変数に格納されている状態)。

typeの継承

ただそれだと metaclass という名称とマッチしないので、実際にはtypeを継承したクラスを作成してそれを __metaclass__ に指定するのが一番自然なのではないかと思います。 その場合、 __metaclass__ に指定されたクラスのインスタンスとしてクラスが作成されるようになるので、 クラス作成をカスタマイズするには __metaclass__ に指定したクラスの __new__ もしくは __init__ をカスタマイズすることになります。

type__init__ ではなく __new__ メソッドの方でクラス生成の主な作業を行っています (typeobject.c ソースコード)。 そのため、 type の挙動をカスタマイズするには、普段オーバーライドする __init__ ではなくて、 __new__ メソッドをオーバーライドする必要があります。 そして type.__new__ を呼び出す前に引数の name, bases, dict を編集してクラス生成をカスタマイズすることになります。

class mymeta(type):
  def __new__(cls, name, bases, dict):
    # TODO: customize name, bases, dict.
    type.__new__(cls, name, bases, dict)

メタクラスの例

__metaclass__ でどんなことができるのか理解するには例をみてみるのが一番だと思うので、 メタクラスのサンプルとして、__metaclass__ に指定すると getter/setter っぽい名前のメソッド (e.g. get_name, getName, SetName) を自動的に プロパティ に変換してくれるメタクラス auto_property (ソースコード) を作成しました。 例えば get_x というメソッドを持つクラスに指定すると、アクセスすると get_x が呼び出されるプロパティ x が自動的に生成されます。 (逆に get_x はメソッドから消えます)

class C(object):
  __metaclass__ = auto_property

  def get_x(self):
    return 123

c = C()
assert c.x == 123

実装の解説

まずは type を継承したクラス auto_property を定義します。 そして __new__ の中で引数として受け取ったクラス定義の辞書 dict をカスタマイズしてから、 type.__new__ を呼び出します。

class auto_property(type):
  def __new__(cls, classname, bases, dict):
    # TODO: dict から setter/getter っぽい名前のメソッドを取り除いて、
    #       代わりに対応する property を持つ new_dict を作成する
    return type.__new__(cls, classname, bases, new_dict)

後は、 TODO の部分で dict に対して for文を回して正規表現を使って getter/setter と property 名を取り出して、 最後に new_dict に対して property(getter, setter) を代入するだけです。

class auto_property(type):
  def __new__(cls, classname, bases, dict):
    new_dict = {}
    setters = {}
    getters = {}
    properties = set()
    for name in dict:
      value = dict[name]
      # TODO: setter/getter を正規表現で検出

    for property_name in properties:
      getter = getters.get(property_name, None)
      setter = setters.get(property_name, None)
      new_dict[property_name] = property(getter, setter)
    return type.__new__(cls, classname, bases, new_dict)

githubにソースコードをコミットしてある ので、実装の細かい部分はそちらを確認して下さい。

最終更新: 2015/1/14