Pythonのメタクラスについて

最近どっかでメタプログラミングが流行っているようなので、Pythonでもひとつ。

classobject

一番単純な例として、classもオブジェクトであるということ。

>>> class A(object):
...     """ A sample class """
...
>>> A
<class 'A'>
>>> AA = A
>>> a = A()
>>> isinstance(a, A)
True

A はクラスであり、オブジェクトでもある。 Aclass 文で作成されているわけで、 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メソッドを作成する。 これから作成するメタクラスは、クラス宣言中の文字列を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')]

Table Of Contents

This Page