2010年11月3日水曜日

Python 3のデザインパターンでメタクラスしてみた(2)

前回の続きです。

・メタクラス
メタクラスとは一体何なのでしょうか?

その詳細については、@aodagさんの「Pythonのメタクラスについて」という文書を読んでください……これは、メタクラスをどうやって説明しようか散々悩んだ末、Python初心者の私が内容の怪しい説明をするよりは、上級者の方のちゃんとした説明を読んで頂いた方がいいに違いない!と信じたからであって、メタクラスを説明しようとしたらめちゃくちゃ面倒臭いし、さっぱり分からなかったので手抜きをしようとした訳ではありません!!ので、くれぐれも誤解のなきようお願い致します!!!(←言い訳)

ただ、この資料はPython 2系向けの資料なので、サンプルコードをそのままPython 3で実行するとエラーが発生します。
念のため、サンプルコードをPython 3向けに修正しつつ実際に実行してみた結果を下記に掲載しますので、恐れ入りますが、適宜読み替えてください。


$ python3.1
Python 3.1.2 (r312:79147, Oct  6 2010, 11:35:08) 
[GCC 4.2.1 (Apple Inc. build 5664)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class A:
...     """ A sample class """
... 
>>> A
<class '__main__.A'>
>>> AA = A
>>> a = A()
>>> isinstance(a, A)
True
>>> type(a)
<class '__main__.A'>
>>> A
<class '__main__.A'>
>>> type(A)
<class 'type'>
>>> MyPerson = type('MyPerson', (), {})
>>> MyPerson
<class '__main__.MyPerson'>
>>> class MyPerson:
...     pass
... 
>>> MyPerson
<class '__main__.MyPerson'>
>>> type(type)
<class 'type'>
>>> class MyType(type):
...     pass
... 
>>> MyType('MyTypedPerson', (), {})
<class '__main__.MyTypedPerson'>
>>> class MyType(type):
...     def __init__(cls, name, bases, dct):
...         super().__init__(name, bases, dct)
...         if 'message' in dct:
...             print(dct['message'])
... 
>>> MyTypedPerson = MyType('MyTypedPerson', (), {'message':'hello'})
hello
>>> class MyType2(type):
...     def __init__(cls, name, bases, dct):
...         super().__init__(name, bases, dct)
...         def new_init(self, **kwargs):
...             for k, v in [(k, v) for (k, v) in kwargs.items() if k in dct['attributes']]:
...                 setattr(self, k, v)
...         cls.__init__ = new_init
... 
>>> MyTypedPerson2 = MyType2('MyTypedPerson', (), {'attributes':['name', 'description']})
>>> p2 = MyTypedPerson2(name='msmhrt', description='author of this document', other='ignore this argument')
>>> p2.name, p2.description
('msmhrt', 'author of this document')
>>> class MyTypedPerson3(metaclass=MyType2):
...     pass
... 
>>> p3 = MyTypedPerson2(name='msmhrt', description='author of this document', other='ignore this argument')
>>> p3.name, p2.description
('msmhrt', 'author of this document')
>>> 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
... (
...     'msmhrt'
... )
... """)
>>> c.close()
>>> class SQLQueryType(type):
...     def __init__(cls, name, bases, dct):
...         super().__init__(name, bases, dct)
...         for k, v in [(k, v) for (k, v) in dct.items() if not k.startswith('_') and isinstance(v, str)]:
...             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
... 
>>> class MyQuery(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, 'msmhrt')]
>>> q.insert_new('John Doe')
[]
>>> q.select_all()
[(1, 'msmhrt'), (2, 'John Doe')]
>>> q.update('msmhrtx', 1)
[]
>>> q.select_all()
[(1, 'msmhrtx'), (2, 'John Doe')]
>>> 

以下、Python 3での変更点です。
  • Python 3ではいわゆる旧クラスが廃止されて新クラスだけになった結果、クラスが常にobjectクラスを継承するようになったので、class文でスーパークラスとしてobjectクラスを指定する必要がなくなった。同じ理由でtype()の第2引数でobjectの指定を省略可能。
  • Pythonの対話モードではモジュールを指定しないオブジェクトはメインモジュールである __main__ に所属する。(これはPython 2系でも同じだったはず)
  • type(A) の結果が、<type 'type'>ではなく、<class 'type'> と表示されるようになった。
  • super(MyType, cls) 等の代わりに、super() という記法が使えるようになった。今回はメタクラス中で super() を使っているので、第2引数が cls になっているが、super(MyPerson, self) 等も super() で代替可能。
  • print文がprint関数になった。
  • dict型の iteritems() メソッドが廃止された代わりに、items() メソッドがイテレータを返すようになった。
  • メタクラスをクラスの本体で "__metaclass__ = MyType2" と指定するのではなく、class文で"class MyTypedPerson3(metaclass=MyType2):" のように指定するようになった。
  • basestring型が廃止されたので、今回は isinstance(v, basestring) を isinstance(v, str) に置き換えた。
  • unicode型も廃止されたので、u'John Doe' が 'John Doe' と表示されている。

少し長くなったので、前回のFlyweight パターンでメタクラスを使った場合のコードについては、次回に回します。

(続く)

0 件のコメント:

コメントを投稿