Kyoto CabinetをPythonで試す

はじめに

SQLが嫌いなので、TerminatterでMongoDB使ってるぽんこつさんだけど、気になったのでKey Value Store型で最も早い(らしい)Kyoto Cabinetを試そうかと思ったけど最初のexampleで躓いたお話。

Kyoto Cabinetのバインディングがダサい

Kyoto CabinetのExampleの1つを見て欲しい。

from kyotocabinet import *
import sys

# create the database object
db = DB()

# open the database
if not db.open("casket.kch", DB.OWRITER | DB.OCREATE):
    print("open error: " + str(db.error()), file=sys.stderr)

# store records
if not db.set("foo", "hop") or \
        not db.set("bar", "step") or \
        not db.set("baz", "jump"):
    print("set error: " + str(db.error()), file=sys.stderr)

# retrieve records
value = db.get_str("foo")
if value:
    print(value)
else:
    print("get error: " + str(db.error()), file=sys.stderr)

# traverse records
cur = db.cursor()
cur.jump()
while True:
    rec = cur.get_str(True)
    if not rec: break
    print(rec[0] + ":" + rec[1])
cur.disable()

# close the database
if not db.close():
    print("close error: " + str(db.error()), file=sys.stderr)

超絶にダサい。何がダサいかというと、

  • ifで返り値をチェックしてエラー検出している。Python使いなら例外を返すべき
  • cursorがもうどこから突っ込んでいいのか分からないぐらい酷い。MongoDBみたくiterator(みたいなforで回せるもの)を返すべき
  • hop, step, jumpって何だ、spam, ham, eggに決まってるだろ!

といった辺り。

バインディングのラッパー

PythonianがPythonらしいバインディングに書き換えるラッパーを書いてみた。とりあえず目についたのだけ書き換えたので全くもって不完全だが、Pythonを齧ったことある人なら方針は明快だと思う。

import kyotocabinet as kc

class KyotoCabinet(kc.DB):
    def __del__(self):
        self.close()

    def open(self, *args, **kwds):
        if not super(KyotoCabinet, self).open(*args, **kwds):
            raise IOError("Open error: {0}".format(super(KyotoCabinet, self).error()))

    def set(self, *args, **kwds):
        if not super(KyotoCabinet, self).set(*args, **kwds):
            raise IOError("Set error: {0}".format(super(KyotoCabinet, self).error()))

    def close(self, *args, **kwds):
        if not super(KyotoCabinet, self).close(*args, **kwds):
            raise IOError("Close error: {0}".format(super(KyotoCabinet, self).error()))

    def cursor(self, *args, **kwds):
        cur = super(KyotoCabinet, self).cursor(*args, **kwds)
        cur.jump()
        while 1:
            rec = cur.get_str(True)
            if not rec: break
            yield rec
        cur.disable()

open, close, setが例外を投げるように、あとcursorをジェネレータ式に書き換えた。パフォーマンス的な問題は出るかもしれないが、あんなダサいのは見ていて耐えられない。

ぽんこつ式example

結果、あのかったるい例が

db = KyotoCabinet()
db.open("sample.kch", kc.DB.OWRITER | kc.DB.OCREATE)
db.set("1", "spam")
db.set("2", "ham")
db.set("3", "egg")
print(db.get_str("2"))
for rec in db.cursor():
    print(rec[0], ":", rec[1])

こんなに短くなったよ!やったね!