記事一覧:2009年11月20日

Pythonネタ:クラスメソッドを動的に生成する

 Pythonには動的に関数を作ったり、クラスのメソッド呼び出しをカスタマイズするための機構が用意されています。ということで、それを使って遊んでみた。

 まず、Pythonではクラスで__getattr__メソッドを定義することで、クラスに対する任意の属性値アクセスを横取りすることができる。

 Pythonのクラスやオブジェクトは、それぞれが持つ属性(クラスならメンバ関数/変数)を__dict__というdictionary内に格納している。たとえばとあるオブジェクトaに対し「a.x」という操作を行うと、まずPython処理系はそのオブジェクトが持つ__dict__内で「x」という名前を持つメンバ変数/関数を探し、存在しなければ続いてそのオブジェクトのクラスが持つ__dict__内、続いてそのクラスの派生元クラスの__dict__内、という順でメンバ変数/関数を探索する。

 この作業を行って、通常すべての__dict__内で該当する名前が存在しなければエラーとなり例外を発生させるのだが、もしオブジェクト/クラス/派生元クラス内で__getattr__()メソッドが定義されていた場合、例外を発生させずに__getattr__()メソッドが呼び出される仕組みになっている。

 まぁ、サンプルコードを見たほうが分かりやすいかと。下記は、メソッドを呼び出すと、そのメソッド名を返すというクラス「echo」の例。

$ python
Python 2.6.1 (r261:67515, Jul  7 2009, 23:51:51)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class echo(object):
...     def __getattr__(self, name):
...             return lambda: name
...
>>> o = echo()
>>> o.hoge()
'hoge'
>>> o.foobar()
'foobar'
>>> o.abcdefg()
'abcdefg'

 __getattr__()メソッドは、あるオブジェクトの属性値/属性メソッドにアクセスしようとした際に、そのオブジェクト/クラス/派生元クラス内で目的となる属性が見つからなかった場合に呼び出される。引数nameにはアクセスしようとした属性の名前が与えられる。ここではlambda構文を使用し、引数nameを返す関数を__getattr__の戻り値とした。

 また、オブジェクトの__dict__属性に値を追加することで、動的に任意の属性をオブジェクトに追加できる。

 下記は、オブジェクトにメソッドを追加するメソッド「teach」を備えたクラス「echo2」の例。

>>> class echo2(object):
...     def teach(self, key, value):
...             self.__dict__[key] = lambda : value
...
>>> o = echo2()
>>> o.hoge()
Traceback (most recent call last):
  File "", line 1, in
AttributeError: 'echo2' object has no attribute 'hoge'
>>> o.teach("hoge", "foobar")
>>> o.hoge()
'foobar'
>>> o.teach("abcdefg", "alphabet")
>>> o.abcdefg()
'alphabet'

 このへんの仕組みがどのような場合に有効化というと、たとえば単純なメソッドを大量に用意したい場合。下記は、「False」を返すis_a()〜is_z()までを一気に定義するというもの。

>>> for i in "abcdefghijklmnopqrstuvwxyz":
...     o.__dict__["is_"+i] = lambda : False
...
>>> o.is_a()
False

 覚えておくと、たまにラクができるかも。