Hyper EstraierのAPIをPythonのctypes経由で呼び出す

| | Comments (0) | TrackBacks (0)
Python 2.5から、ctypesというライブラリが標準ライブラリに追加されました。このライブラリは、Cの関数をPython用にラッピングしてくれるライブラリで、PythonからCのライブラリ(.soとか.aとか.dllとか、環境による)中の関数を「直接」呼び出すことができます。

似たような仕組みには、SWIGや、PythonのC拡張モジュールなどがありますが、ctypesはそれらの仕組みと比べ、圧倒的に手軽なのが売りです(だと思います)。

たとえば、Python 2.5以上であれば、インタラクティブモード上で、

>>> from ctypes import *
>>> libc = CDLL("libc.so.6")
>>> libc.puts("hello ctypes")
hello ctypes
13

のように、いきなりlibcのputs関数を呼び出すことができます(インタラクティブモードなので、戻り値の「13」が表示されています)。
もちろん引数も渡せます。

    >>> libc.printf("My name is %s and I'm %d years old\n", "rgoura", 18)
    My name is rgoura and I'm 18 years old
    39


戻り値も手軽に扱えます。

>>> print "%d letters" % libc.strlen("HDE")
3 letters

整数と文字列は気にせず利用することができるよう工夫されており、その他のデータ型や、C特有の概念である構造体やポインタについても、工夫すれば大抵のことができるようになっています。


Hyper EstraierのAPIをctypes経由で呼び出す

さて、私は過去の自分宛のメールをHyper Estraierでインデックス化して検索できるようにしているのですが、先日、日常で使うちょっとしたプログラムをPythonで書いてみよう、と思い立ちました。

Hyper EstraierにはJava, Ruby, PerlのAPIバインディングは標準で提供されているのですが、Python用のバインディングはhyper_estraier_wrappersというサブプロジェクトとして扱われています。じゃあそれを使えばいいじゃん、という話なんですが、かねてからctypesをいじってみたかったので、あえて ctypesをいじってみることにしました。

手始めに、Hyper Estraierの「プログラミングガイド」にある、「サーチャのサンプル」(http://hyperestraier.sourceforge.net/pguide-ja.html#searcher)をpython+ctypesに移植してみたところ、結果は良好で、サンプルの意図通りに動作しました。

そこで、以下に移植した結果を示し、ちょっと迷ったところやおもしろかったところ、わからなかったところにコメントをしていきたいと思います。基本的に上記の「サーチャのサンプル」を一行一行置き換える形で移植しましたので、見比べていただければわかりやすいと思います。


実際のコードとコメント

     1    #!/usr/bin/python2.5
     2    # vim:fileencoding=utf_8  
     3    import sys
     4    from ctypes import *
     5    
     6    # enums were in 'estraier.h'
     7    ESTDBREADER = 1
     8    
     9    est = CDLL("libestraier.so")
    10    
    11    ecode = c_int()
    12    
    13    # データベースを開く
    14    db = est.est_db_open("casket", ESTDBREADER, byref(ecode))
    15    if not db:
    16        func = est.est_err_msg
    17        func.restype = c_char_p
    18        print >>sys.stderr, "error: %s" % func(ecode)
    19        sys.exit(1)
    20    
    21    # 検索条件オブジェクトを生成する
    22    cond = est.est_cond_new()
    23    
    24    # 検索条件オブジェクトに検索式を設定する
    25    est.est_cond_set_phrase(cond, "rainbow AND lullaby")
    26    
    27    # データベースから検索結果を得る
    28    func = est.est_db_search
    29    func.restype = POINTER(c_int) # int *は基本型としては定義されてない
    30    resnum = c_int()
    31    result = func(db, cond, byref(resnum), None)
    32    
    33    # 各該当文書を取得して表示する
    34    for i in xrange(0, resnum.value):
    35        # 文書オブジェクトを取得する
    36        doc = est.est_db_get_doc(db, result[i], 0)
    37        if not doc:
    38            continue
    39    
    40        # 属性を表示する
    41        func = est.est_doc_attr
    42        func.restype = c_char_p
    43        value = func(doc, "@uri")
    44        if value:
    45            print "URI: %s" % value
    46    
    47        value = func(doc, "@title")
    48        if value:
    49            print "Title: %s" % value
    50    
    51        # 本文を表示する
    52        texts = est.est_doc_texts(doc)
    53        for j in xrange(0, est.cblistnum(texts)):
    54            func = est.cblistval
    55            func.restype = c_char_p
    56            value = func(texts, j, None)
    57            print "%s\n" % value
    58    
    59        # 文書オブジェクトを破壊する
    60        est.est_doc_delete(doc)
    61    
    62    # 検索結果を破壊する
    63    libc = CDLL("libc.so.6")
    64    libc.free(result)
    65    
    66    # 検索条件オブジェクトを破棄する
    67    est.est_cond_delete(cond)
    68    
    69    # データベースを閉じる
    70    res = est.est_db_close(db, byref(ecode))
    71    if not res:
    72        func = est.est_err_msg
    73        func.restype = c_char_p
    74        print >>sys.stderr, "error: %s" % func(ecode)
    75        sys.exit(1)
    76    
    77    sys.exit(0)

7行目:
enum定数は、必要なものをヘッダファイルから書き写したりしてくるしかないと思います。

14行目:byref(ecode)のところ
(C言語の&ecodeのような)参照渡しをするためには、byref(ecode)を使います。

16行目:
ctypesの若干面倒なところで、何も指定しない場合、関数の戻り値はc_int型(整数型)です。c_int型以外の戻り値を設定するためには、関数オブジェクトのrestypeプロパティに戻り値の型を設定する必要があります。そのため関数オブジェクトをいったんfuncに代入しています。この例では、エラー文字列を返してもらうため、17行目にcharポインタを戻り値に設定しています。

22行目:
est_cond_newの戻り値は検索条件オブジェクト(ESTCOND型)であるにもかかわらず、この場合condはpython側からはc_int 型になります。が、今回のプログラムではpython側でcondの値を表示したりする計算したりする機能は存在せず、アドレスさえ保持されていればさしつかえないため、特に16行目のようなことは行っていません。14行目、36行目、52行目なども同様です。

29行目:POINTER(c_int)のところ
char *を表すc_char_pなどはctypesの基本データ型として定義されていますが、int *を表す基本データ型はありません。一瞬迷いましたが、POINTER(c_int)で大丈夫でした。

34行目:resnum.valueのところ
c_int型の値をPython側から使うためには、valueメンバを参照します。

36行目:result[i]のところ
POINTER(c_int)型のデータは、添え字インデックスで各要素にアクセスできます。

62行目:
元のプログラムでは、
free(result);
となっている箇所です。pythonにはfreeは無いので(あったとしても違う場所を解放すると思われます)、libcからfreeを呼び出しました。


感想

途中でCのプログラム内容を再現できない内容が出現して挫折するのではないかと思いましたが、無事再現できました。この手軽さはすごい。

ただし、私の未熟さも祟って、この規模のコードでさえ上記の姿に完成するまでにSegmentation faultに何度か見舞われ、普段からいかにぬるい風呂に浸かっているかを思い知らされました(^^;

もはやバインディングはいらない?というのはあきらかに言い過ぎですが、ちょっとしたプログラムに活用できるかもしれませんね。


(参考)
Hyper Estraier 「プログラミングガイド」(サーチャのサンプル)
http://hyperestraier.sourceforge.net/pguide-ja.html#searcher

ctypesのチュートリアル
http://www.python.jp/doc/release/lib/ctypes-ctypes-tutorial.html

0 TrackBacks

Listed below are links to blogs that reference this entry: Hyper EstraierのAPIをPythonのctypes経由で呼び出す .

TrackBack URL for this entry: http://lab.hde.co.jp/blog/mt-tb.cgi/74

Leave a comment

About this Entry

This page contains a single entry by rgoura published on December 15, 2008 3:33 PM.

lsコマンドの配色を変更する was the previous entry in this blog.

TwistedをRPM化する小ネタ is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.