Senna DB APIのPython bindingsをCythonを使って書く

id:shnに「PyrexでPyTC書き直してみたら?」と言われて、
id:moriyoshiに「Cythonのほうがいいお」と言われたので(どちらも意訳)、
Cythonをちょこちょこと触っていた。


Senna DB APIのbindingsを書いてみた。
一応説明しておくと、Senna DB APIはkey-record型のDBです。
key-value型だと直に値を保持します(ex. TokyoCabinet etc.)。
key-record型はrecordの中にカラムが複数定義できます。
あるカラムで絞り込みをしつつ別のカラムでソートして
上位n件のkey-recordを取り出す、
ということができたりするのがkey-value型に比べたメリットです。
key-record型っていう用語は超怪しいですけどね…


俺的Q&A

Pyrexとどこが違うのよ?

いろいろあるみたいだけど、
僕が気に入っているのは

  • bint型
  • classの中身でコードがちゃんと実行できる
  • エラーが超親切

の3点です。

bint型は、bool的なintを格納する型です。
classの中身でコードが実行できるのは、以下のようなことができるってことですね。
classmethodについては、たぶんデコレータは使えないと思う。試してないけど。

  cdef class Blah:
    def some_method(self):
      print self
    some_method = classmethod(some_method)
    a = 2*3
    print "hi", a

エラーが超親切なのは言わずもがな。

Pythonの__init__を呼ばずにインスタンス作りたい

C言語のライブラリ側で何かのインスタンスを表すポインタが得られるとします。
このポインタを保持するPythonオブジェクトを作りたいとします。
普通にCythonでオブジェクトを作ると、__init__が呼ばれてしまいます。
ということは、ポインタ->Pythonのobject->ポインタという無駄な変換をしないといけないわけです。
直にオブジェクト作りたいっすよね。


以下のようにPY_NEWを定義したヘッダを作っておきます。tekito.hとか。

#define PY_NEW(T) \
     (((PyTypeObject*)(T))->tp_new( \
             (PyTypeObject*)(T), __pyx_empty_tuple, NULL))

んで、以下のようにするとC言語側でPythonのオブジェクトを作ってあげることができます。
ファクトリメソッドが外に出ちゃうのはあんまり気にしないで。

cdef class Tekito:
    cdef void *ptr
    def __init__(self):
        raise TypeError('This class cannot be instantiated from Python')

cdef extern from "tekito.h":
    cdef object NEW_CLASS "PY_NEW" (object t)

cdef ExampleClass _factory(void *ptr):
    cdef Tekito instance
    instance = NEW_CLASS(Tekito)
    instance.ptr = ptr
    return instance

初期化はどうするのよ?

普通にモジュールトップで関数を呼び出してください。

終了処理はどうするのよ?

atexit使ってください。

import atexit
def fin_function():
  fin_c_function()

atexit.register(fin_function)

==cdefで定義された関数も直にregisterできます。ステキですね。==
うそでした。お詫びして訂正します。上のコードは訂正後です。
以前のCythonでは、どうやら特有のatexitの代わりを行う__Pyx_RegisterCleanup()という関数が挿入されていたようです。
(Py_AtExitは16個しか関数が登録できないということで)。
手元のCython 0.9.8では、Pythonのatexit.registerを呼んでいるようです。


というわけで、今書くならPyrexよりCythonダネッ!
とりあえずコミットしたので、
memcachedプロトコルの実装をlibevent+Pythonでやってみるか。