[python]メタプログラミングの基礎(__init__, __new__, __metaclass__)
python の __init__
, __new__
, __metaclass__
や、それらを使ったメタクラスの作成方法などについて。
「メタプログラミング」という言葉はたぶんこれ以外の機能についても言えるんだけど、参考にしたサイトが大体メタプログラミングという言葉を使っていたので、ここでもそうする。
こちらの記事が非常に参考になりました。
Python の メタプログラミング (__metaclass__, メタクラス) を理解する | yunabe.jp
細かい説明は上の記事に書かれているので、ここではポイントだけメモ。
ポイント
- クラス定義は
type
のインスタンスである MyClass()
という書き方でクラスのインスタンスを生成したとき、内部では__new__
→__init__
の順に呼ばれる__new__
の内部ではtype
のインスタンス化(type(cls, names, attrs)
の呼び出し)が行われている- クラスに
__metaclass__
という属性があると、type
のインスタンス化のかわりに実行される
__metaclass__
の基本的な使い方としては:
type
を継承したクラスAを定義する(メタクラス)- 別のクラスBを定義し、メタクラスAをクラスBの
__metaclass__
属性に指定する - → クラスBがインスタンス化された時、クラスAの
__new__
メソッドが呼ばれる。結果としてクラスBのインスタンス作成時の振る舞いをカスタマイズできる
以下、ポイントを順に見ていく。
クラス定義は type
のインスタンスである
以下 (1) と (2) は同じ挙動となる。
# -*- coding: utf-8 -*- class Foo(object): pass # (1) 通常のクラス定義 class Bar(Foo): attr1 = 'bar' def method1(self, *args): print 'method1' def func1(self, *args): print 'method1' # (2) type を使ったクラス定義 # type(クラス名, 親クラス, 属性・メソッドの辞書) Baz = type('Baz', (Foo,), {'attr1': 'baz', 'method1': func1}) bar = Bar() bar.method1() print type(Bar) # True print isinstance(Bar, type) # True baz = Baz() baz.method1() print type(Baz) # True print isinstance(Baz, type) # True
ので、クラスを定義すると内部的には type('クラス名', ...)
が実行されてクラスが生成されていることになる。
MyClass()
という書き方でクラスのインスタンスを生成したとき、内部では __new__
→ __init__
の順に呼ばれる
__new__
の内部では type
のインスタンス化(type(cls, names, attrs)
の呼び出し)が行われている
python のクラスの初期化は通常 __init__
で行い、このメソッドを他の言語で言うところのコンストラクタのように利用することが多い。
が、インスタンス生成時には __init__
の前に __new__
が呼ばれている。
詳細な順番は http://www.yunabe.jp/docs/python_metaclass.html から引用させていただく。
class ClassName
がClassName()
によってインスタンス生成された場合- まず
ClassName.__new__
が第1引数にClassName
, 残りの引数にClassName
に与えた残りの引数が与えられて呼び出される。- 普通は
__new__
は定義されていないので親クラスをたどっていって最終的にobject.__new__
が呼び出される。object.__new__
は第1引数で与えられた クラスのインスタンスを生成して返す。object.__new__
が返すインスタンスは__init__
が実行される前の未初期化のインスタンスである点に注意。__new__
がClassName
のインスタンスを返した場合に限りClassName.__init__
が呼び出される。
また 公式ドキュメント の __new__
についての説明から引用すると
クラス cls の新しいインスタンスを作るために呼び出されます。
__new__()
は静的メソッドで (このメソッドは特別扱いされているので、明示的に静的メソッドと宣言する必要はありません)、インスタンスを生成するよう要求されているクラスを第一引数にとります。残りの引数はオブジェクトのコンストラクタの式 (クラスの呼び出し文) に渡されます。__new__()
の戻り値は新しいオブジェクトのインスタンス (通常は cls のインスタンス) でなければなりません。
(中略)
__new__()
が cls のインスタンスを返した場合、__init__(self[, ...])
のようにしてインスタンスの__init__()
が呼び出されます。このとき、 self は新たに生成されたインスタンスで、残りの引数は__new__()
に渡された引数と同じになります。
__new__()
が cls のインスタンスを返さない場合、インスタンスの__init__()
メソッドは呼び出されません。
クラスに __metaclass__
という属性があると、type
のインスタンス化のかわりに実行される
先ほど、クラスを定義すると内部的には type('クラス名', ...)
が実行されると書いたが、クラスに __metaclass__
という属性があるとこの内部的に実行される処理を別の関数で置き換えることができる。
__metaclass__
を利用するケースとしては、type
を継承したクラスを用意し、別のクラスの __metaclass__
に指定するというのが一番自然である。
あまり実用的ではないけど、サンプル。
class MyMetaClasss(type): def __new__(cls, name, bases, attrs): attrs['foo'] = 'metacls was here' return type.__new__(cls, name, bases, attrs) class MyClass(object): __metaclass__ = MyMetaClasss def bar(self): print self.__dict__ my_class = MyClass() print my_class.foo # 'metacls was here'
コードによってはメタクラスの第一引数は mcs
と書いたり、attrs
でなく dict
と書いている。
また、__new__
内では type.__new__
を呼んでいるコードと super(MyMetaClass, cls).__new__
というように親クラスを呼ぶように書いているコードと両方見られた。
(挙動は変わらないはず。変わるとしたらどういったケースか...?)
リファレンス
公式より
その他、参考にさせていただいた記事