2009年10月17日土曜日

PythonのC++インターフェイスboost.pythonの使用例

少し前に、PythonのC/C++へのインターフェイス作成手段としてはctypesとboost.pythonが良さそうだという話を書いた。その後ctypesの使用例をメモった。

すでにCで開発されたライブラリがある場合にはctypesを使った方が楽だと思う。しかしC++や、あるいは自分でさらから書く場合にはC++を使いたいところ。場合によっては基本的にはC++で開発し、その後より楽に使用するためにPythonからも使えるようにしたい事もある。
そういう訳で、個人的にはPythonのC/C++インターフェイス開発にはboost.pythonが一番だと思う。Python自体に標準的に付随するものではない点だけが気になるところだが、とても簡単にC++のプログラムへのインターフェイスが作成できる。

というわけで、boost.pythonの使用例をメモ。

まず、C++で適当にクラスでも作る。ここでは簡単のためにsuffixarray.hhというヘッダファイルだけ。

// suffixarray.hh
#include <iostream>
#include <algorithm>
using namespace std;

class SuffixarrayCmp : binary_function<const int,const int,bool> {
protected:
    const wchar_t* _buff;
    
public:
    SuffixarrayCmp(const wchar_t* buff) : _buff(buff) {};
    bool operator()(const int x, const int y) const
    {
 return wcscmp(_buff+x, _buff+y) < 0;
    }
};

class SuffixArray {
protected:
    wchar_t* _buff;
    int      _size;
    int*     _idx;

public:
    SuffixArray() : _buff(NULL), _size(0), _idx(NULL)
    {};
    ~SuffixArray() {
 if (_idx != NULL) delete[] _idx;
    };

    bool construct(wchar_t* buff, const int size) {
 _buff = buff;
 _size = size;
 _idx = new int[_size];
 if (_idx == NULL)
     return false;

 for (int i=0; i<size; i++) {
     _idx[i] = i;
 }
 sort(_idx, _idx+size, SuffixarrayCmp(_buff));
 return true;
    };
    
    int getSize() const {
 return _size;
    };
};
このプログラムはC++として完全に独立していて、Pythonとは関係なくコンパイルもできるし利用もできる。wchar_t型(ワイド文字)の配列、要するに日本語文字列などを想定して、そのサフィックスアレイを作るもの。日本語は今はUTF-8を使う事が多いようだけど、メモリ節約にはなってもランダムアクセスには不便なのでwchar_tの配列とした。
また、私の使っているPython(2.5.4 on Debian GNU/Linux x86_64)ではunicode文字列はUTF-16で表現しwchar_t配列に格納しているようなのでそれに合わせたという意味もある。
そうでない場合に対応するにはテンプレートにでもしておけば良いのかな。そういう意味もあってヘッダファイルのみにした。

次に、これをPythonから使えるようにする必要がある。
construct()メソッドにはwchar_t*を渡す必要があるので、Pythonのunicodeからの橋渡しをする必要もある。
そのためのファイルがsuffixarray_py.cc。
// suffixarray_py.cc。
#include "suffixarray.hh"
#include <boost/python.hpp>
using namespace boost::python;

class PySuffixArray : public SuffixArray {
public:
    bool construct(PyObject* op) { // op must be a unicode object
 _buff = (wchar_t*)(PyUnicode_AS_DATA(op));
 _size = PyUnicode_GET_SIZE(op);
 return SuffixArray::construct(_buff, _size);
    };
};

int compare(PyObject* x, PyObject* y) { // x, y must be unicode objects
    wchar_t* xp = (wchar_t*)(PyUnicode_AS_DATA(x));
    wchar_t* yp = (wchar_t*)(PyUnicode_AS_DATA(y));

    return wcscmp(xp, yp);
};

BOOST_PYTHON_MODULE(suffixarray)
{
    class_<PySuffixArray>("SuffixArray")
 .def("construct", &PySuffixArray::construct)
 .add_property("size", &PySuffixArray::getSize)
 ;

    def("compare", compare);
};
PySuffixArrayクラスはSuffixArrayクラスのconstruct()メソッドをオーバーライドしているが、その中でPythonのユニコードからwchar_t*本体とサイズを取り出し、SuffixArrayのconstruct()に渡している。
unicode文字列の橋渡しもboost.pythonがやってくれるようになるとこんな面倒も必要ないのだろうけれど...
整数や普通の文字列の橋渡しはboost.pythonが面倒をみてくれるようだ。

BOOST_PYTHON_MODULEというところがPythonから見えるモジュールの定義。
大体見れば分かると思うが、suffixarrayモジュールの中にSuffixArrayというクラスを定義し、construct()メソッドをPython側からも見えるようにしている。
ここでsizeは「プロパティ」である。
実はadd_property()には三つめの引数を渡すこともできるが、その場合には
.add_property("pyname", &cplusclass::getter, &cplusclass::setter)

として、プロパティのgetter/setterを設定できるようになっている。setterを省略するとそのプロパティ(attribute)はread onlyとなる。
最後のcompare()は、クラスではなくC++の関数を取り込むためのもの。

これをコンパイルするわけだが、boost.pythonのチュートリアルだとbjamというビルドツールを使っている。実は以前、boost.pythonを触ろうとした時にそれが嫌で一瞬でやめてしまった事がある。
要するにmakeの代わりだが、最近はJavaだとAnt、boostだとbjamと、「進化」したビルドツールがよく使われるようになってきているようだ。
しかし、面倒くさい...
サブプロジェクトを含むようなプロジェクトのビルドだとか、そういうことのために色々進化しているらしいが、めんどい...
XMLみたいの書かされるのはとてもめんどいです...
あんなもの人間様が手で書くもんじゃないよね....ツール使う?それもめんどい...

ということで時代に取り残されている気もしないでもないけれど、馴染のmakeにした。
Makefileは個人的にあれこれしているので、ここでは実際のコンパイルコマンドがどうなっているかだけ。オプションもboost.pythonのコンパイルに必要なものだけ。
$ g++ -c -o suffixarray_py.o -I/usr/include/python2.5 -fpic suffixarray_py.cc
$ g++ -shared -o suffixarray.so suffixarray_py.o -lboost_python

これでsuffixarray.soというシェアードオブジェクトができる。

これをPythonから使うのはこんな感じ。
>>> import suffixarray as S
>>> s = unicode('これはぺんですがあれはぺんしるです。', 'utf-8') ## 文字コードは環境依存
>>> sa = S.SuffixArray()
>>> sa.construct(s)
True
>>> sa.size
18
>>> sa.size = 10
Traceback (most recent call last):
File "", line 1, in 
AttributeError: can't set attribute
>>> 
もしPythonからの使い勝手が悪ければ、ラッパークラスを作ってもいい。面倒なのでboost.pythonのレベルで解決した方が良さそうだけど。

なお、Pythonのunicodeオブジェクトはimmutableだが、こうしてC++に渡してあげると自由に変更できる。

0 件のコメント: