Views
オブジェクトデータベース
あれこれ
Python でオブジェクトデータベース
- そもそもオブジェクトデータベースって何だ?
- オブジェクト=データ+メソッド(プログラム)
- とりあえずクラスを定義してインスタンスを作る
データベース無し
# trivial counter class class Counter: def __init__(self): self.count = 1 def inc(self): self.count += 1 def read(self): return self.count c = Counter() print c.read() c.inc(); c.inc(); c.inc() print c.read()
- やってみると 最初の print で 1, inc() を3回やった後は 4 になる。
- 何回やっても結果は同じ。Counter をどこかに保存してないから。
Pickle に保存
- Python には pickle (漬物?)というオブジェクト保存方法がある。
# simple counter ... pickle database # trivial counter class import pickle class Counter: def __init__(self): self.count = 1 def inc(self): self.count += 1 def read(self): return self.count try: counter = pickle.load(file('Counter.pck')) except: counter = {} for key in ['abc', 'def', 'xyz']: if not counter.has_key(key): counter[key] = Counter() else: counter[key].inc() for key in ['abc', 'def', 'xyz']: print key, ':', counter[key].read() pickle.dump(counter, file('Counter.pck', 'w'))
- カウンタを3個生成して、ディクショナリに入れている。このディクショナリごと Counter.pck に保存しておく。やってみると、このプログラムを起動するたびにカウントが1ずつ増えていく。
ZODB を使う
- Zope Object Database というくらいだから、オブジェクトデータベース
# simple counter ... zope object database import sys sys.path.insert(0, '/usr/local/zope29/lib/python') from persistent import Persistent from ZODB import DB from ZODB.FileStorage import FileStorage import transaction # trivial counter class class Counter(Persistent): def __init__(self): self.count = 1 def inc(self): self.count += 1 def read(self): return self.count storage = FileStorage('Counter.fs') db = DB(storage) conn = db.open() dbroot = conn.root() if not dbroot.has_key('counter'): from BTrees.OOBTree import OOBTree dbroot['counter'] = OOBTree() counter = dbroot['counter'] for key in ['abc', 'def', 'xyz']: if not counter.has_key(key): counter[key] = Counter() else: counter[key].inc() for key in ['abc', 'def', 'xyz']: print key, ':', counter[key].read() transaction.commit()
- データベースの初期化のあたりとか、(約束事が)やや複雑ではあるが、 基本的には pickle と同じ感覚で、オブジェクト(インスタンス)ごと ごっそりデータベースに保存できる(ように見える)。
ZEO を使う
- ZODB の別の使い方で、ZEO (データベースサーバ)を動かしておいて それに接続しながら利用することもできる。
# simple counter ... zope object database import sys sys.path.insert(0, '/usr/local/zope29/lib/python') from persistent import Persistent from ZODB import DB from ZEO import ClientStorage import transaction # trivial counter class class Counter(Persistent): def __init__(self): self.count = 1 def inc(self): self.count += 1 def read(self): return self.count addr = '127.0.0.1', 8100 storage = ClientStorage.ClientStorage(addr) db = DB(storage) conn = db.open() dbroot = conn.root() if not dbroot.has_key('counter'): from BTrees.OOBTree import OOBTree dbroot['counter'] = OOBTree() counter = dbroot['counter'] for key in ['abc', 'def', 'xyz']: if not counter.has_key(key): counter[key] = Counter() else: counter[key].inc() for key in ['abc', 'def', 'xyz']: print key, ':', counter[key].read() transaction.commit()
- これは、ZEO サーバが動いていないといけないので、 mkzeoinstance.py を使って、インスタンスを作成し、bin/zeoctl start しておく。
- 上の2つのZODB を利用する例では、Zope をインストールしたディレクトリに あわせて、sys.path.insert() の引数を変えておく必要がある。
SQLObject を使う
- SQL データベースというものが世の中ではよく利用されるので、最近は これを Object DB に利用する方法がいくつか出ている。
- SQLObject と SQLAlchemy を取り上げるが、使ったことが無いので間違っているかもしれない。
# simple counter ... sqlobject / postgresql database from sqlobject import * scheme = 'postgres://localhost/sqlobjectcounter' # trivial counter class class Counter(SQLObject): key = StringCol(alternateID=True) count = IntCol() def inc(self): self.count += 1 def read(self): return self.count sqlhub.processConnection = connectionForURI(scheme) try: Counter.createTable() except: pass for key in ['abc', 'def', 'ghi']: try: c = Counter.byKey(key) c.inc() except: Counter(key=key, count=1) for key in ['abc', 'def', 'ghi']: print key, ':', Counter.byKey(key).read()
- とりあえず、postgresql が動いているので利用。createdb しておく必要あり。
- Counter クラスの定義が大幅に異なる。初期化も違う。
- commit する感じのところがないけどいいのかなぁ?
SQLAlchemy を使う
# simple counter ... sqlalchemy / postgresql database from sqlalchemy import * scheme = 'postgres://localhost/sqlalchemycounter' db = create_engine(scheme) metadata = BoundMetaData(db) try: # first time counter_table = Table('counter', metadata, Column('key', String(), primary_key=True), Column('count', Integer) ) counter_table.create() except: counter_table = Table('counter', metadata, autoload=True) # trivial counter class class Counter(object): def __init__(self, key): self.key = key self.count = 1 def inc(self): self.count += 1 def read(self): return self.count countermapper = mapper(Counter, counter_table) session = create_session() query = session.query(Counter) for key in ['abc', 'def']: try: c = query.get_by(key=key) c.inc() except: c = Counter(key) session.save(c) session.flush() for key in ['abc', 'def']: print key, ':', query.get_by(key=key).read()
- メソッドでないアトリビュート(key, count) をあらかじめ SQL のテーブルとして作っておくと、mapper() を使ってクラスの中で参照できるのか。
- ディクショナリで個々のカウンタを参照するのでなく、query.get_by というのを使うらしい。
- 初期化などのオーバヘッドはかなりあるが、クラスの書き方なんかは、本来のものに近いような気がする。
- SQL* は、こんなんでいいのかまだ不明。