最近どっかでメタプログラミングが流行っているようなので、Pythonでもひとつ。
一番単純な例として、classもオブジェクトであるということ。
>>> class A(object):
... """ A sample class """
...
>>> A
<class 'A'>
>>> AA = A
>>> a = A()
>>> isinstance(a, A)
True
A はクラスであり、オブジェクトでもある。 A は class 文で作成されているわけで、 class 文というのは、クラスを作成するための文ということだ。
さて、ここで A の型はなんであろう。
>>> type(a)
<class 'A'>
>>> A
<class 'A'>
>>> type(A)
<type 'type'>
A の型は type である。 A は、 type のインスタンスと言い換えられる。
さらに、 type は、クラスのコンストラクタとして利用できる。
>>> MyPerson = type('MyPerson', (object,), {})
>>> MyPerson
<class 'MyPerson'>
この呼び出しは以下の class 定義文と同等である。
>>> class MyPerson(object):
... pass
>>> MyPerson
<class 'MyPerson'>
つまり、 class 文とは、 type(name, bases, dct) 呼び出しのシンタックスシュガーと言える。
type の、 type は、 type となっている。
>>> type(type)
<type 'type'>
また、 type を親クラスとして、 class 宣言可能である。
>>> class MyType(type):
... pass
...
type のサブクラス MyType を使って、クラスを生成できる。
>>> MyType('MyTypedPerson', (object,), {})
<class 'MyTypedPerson'>
このままでは、特に何も変わらない。 クラス生成時にいくつかの処理を付け加えよう。
>>> class MyType(type):
... def __init__(cls, name, bases, dct):
... super(MyType, cls).__init__(name, bases, dct)
... if 'message' in dct:
... print dct['message']
>>> MyTypedPerson = MyType('MyTypedPerson', (object,), {'message':'hello'})
hello
さらにクラス定義に変更を加えてみよう。
>>> class MyType2(type):
... def __init__(cls, name, bases, dct):
... super(MyType2, cls).__init__(name, bases, dct)
... def new_init(self, **kwargs):
... for k, v in [(k, v) for (k, v) in kwargs.iteritems() if k in dct['attributes']]:
... setattr(self, k, v)
... cls.__init__ = new_init
>>> MyTypedPerson2 = MyType2('MyTypedPerson', (object,), {'attributes':['name', 'description']})
>>> p2 = MyTypedPerson2(name='aodag', description='author of this document', other='ignore this argument')
>>> p2.name, p2.description
('aodag', 'author of this document')
これは、指定したアトリビュートを受け取るコンストラクタを生成する。
class 文でメタクラスを使う場合は以下のように宣言する。
>>> class MyTypedPerson3(object):
... __metaclass__ = MyType2
...
>>> p3 = MyTypedPerson2(name='aodag', description='author of this document', other='ignore this argument')
>>> p3.name, p2.description
('aodag', 'author of this document')
メタクラスによるメソッド生成の例として、SQLメソッドを作成する。 これから作成するメタクラスは、クラス宣言中の文字列をSQLとして実行するメソッドに変換する。
まず下準備
>>> import sqlite3 as db
>>> con = db.connect(':memory:')
>>> c = con.cursor()
>>> c = c.execute("""
... CREATE TABLE person
... (
... person_id INTEGER PRIMARY KEY,
... name varchar(255)
... )
... """)
...
>>> c = c.execute("""
... INSERT INTO person
... (
... name
... )
... VALUES
... (
... 'aodag'
... )
... """)
... c.close()
以下がSQLメソッド生成をするメタクラスである。 また、コネクションを受け取るコンストラクタも生成している。
>>> class SQLQueryType(type):
... def __init__(cls, name, bases, dct):
... super(SQLQueryType, cls).__init__(name, bases, dct)
... for k, v in [(k, v) for (k, v) in dct.iteritems() if not k.startswith('_') and isinstance(v, basestring)]:
... setattr(cls, k, cls.sqlmethod(v))
... cls.__init__ = lambda self, con: setattr(self, 'connection', con)
...
... def sqlmethod(cls, sql):
... def method(self, *args):
... cur = self.connection.cursor()
... try:
... cur.execute(sql, tuple(args))
... return cur.fetchall()
... finally:
... cur.close()
... return method
...
クラス宣言中のアトリビュートから、文字列のものを選択し、`sqlmethod` メソッドで、実際にSQLを実行するためのメソッドを生成している。 _ で始まる名前は対象外にするのは、礼儀として重要だ。
この、 SQLQueryType は以下のように使う。
>>> class MyQuery(object):
... __metaclass__ = SQLQueryType
... select_all = "SELECT * FROM person"
... insert_new = "INSERT INTO person (name) VALUES (?)"
... update = "UPDATE person SET name = ? WHERE person_id = ?"
...
>>> q = MyQuery(con)
>>> q.select_all()
[(1, u'aodag')]
>>> q.insert_new('John Doe')
[]
>>> q.select_all()
[(1, u'aodag'), (2, u'John Doe')]
>>> q.update('aodagx', 1)
[]
>>> q.select_all()
[(1, u'aodagx'), (2, u'John Doe')]