ノート/テキストマイニング
訪問者数 1760      最終更新 2009-02-28 (土) 18:55:44

>>ノート/テキストマイニング/oregano
>>ノート/テキストマイニング/oregano3

テキストマイニング@Oreganoの設定2

2009/02/26

pythonのラッパー

例えばこれ参照

まずは例題で試してみよう。上記ページとほぼ同じでつまらないが、我慢・我慢。
まず、呼ばれるプログラム。将来はこれがchasenのモジュール呼び出しになるはずだが、
今は簡単な1〜nの加算プログラムsum_body.c。nを引数として与えると、Σnを計算して、戻り値として返す。

int sum(int n) {
  int i, result;
  result = 0;
  for (i=1; i<=n; i++) {
    result = result+i;
  }
  return result;
}

次に、これに対するwrapperプログラムsum_wrapper.c。

#include "Python.h"    //これは/usr/include/python (/usr/include/python2.5)から引いてくる
extern int sum(int);   //これは呼び出される本体。別ファイルなのでextern宣言。

PyObject* sum_sum(PyObject* self, PyObject* args)  // Pythonで呼び出すオブジェクト。argsについては下記。  
{
  int n, result;
  if (!PyArg_ParseTuple(args, "i", &n))   // 呼出し時のpythonからの引数の解析。"i"は下記。キーワード引数の場合も下記。
     return NULL;      //引数解析に失敗するとこのオブジェクトも失敗
  result = sum(n);     //本体の呼出し。nを与え、resultをもらう
  return Py_BuildValue("i", result);  //戻り値をpythonへ戻す。"i"は下記。
}
//   初期化関連で、2つ用意。
static PyMethodDef summethods[] = {   // 関数〜メソッドの対応テーブル
  {"sum", sum_sum, METH_VARARGS},  // メソッド名sum、本体関数sum_sum、引数方式はMETH_VARARGS(位置引数)のみ
  {NULL},                          // METH_VARARGS|METH_KEYWORDSのようにもできる
};

void initsum()
{
   Py_InitModule("sum", summethods);  //初期化関数。モジュールsumに対してinitsumにする。summethodは対応テーブルで。
}

引数や戻り値の形式(表し方)は、"c", "s", "i", "l"(long), "f"(float), "d"(double)などがある。"i"が3つならば、"iii"のように。詳細は別途。

これで、コンパイルは次のようにする。

gcc -fpic -o sum_body.o -c sum_body.c
gcc -fpic -I/usr/include/python -o sum_wrapper.o -c sum_wrapper.c
gcc -shared sum_body.o sum_wrapper.o -o summodule.so

このとき、最後のshared moduleを作成するときに、もしモジュール名がsumならば、ファイル名をsummodule.soにする。
includeのパスは、/usr/include/python2.5だが、/usr/include/pythonにリンクを張って置いた。

最後にpythonから試してみる。

$ python
Python 2.5.2 (r252:60911, Sep 30 2008, 15:41:38)
[GCC 4.3.2 20080917 (Red Hat 4.3.2-4)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sum
>>> sum.sum(10)
55
>>> sum.sum(100)
5050
>>> sum.sum(1000)
500500

モジュールsumをインポートすることができ、その中のメソッドsumを呼び出すことができた。これにて例題は一応成功。

chasenのライブラリーコール型実行を試してみよう

chasenをC言語のライブラリとして実行する機能がある。ラッパーはこれを使うつもり。
まず、C言語のプログラムとしてうまく使えるか確認する。
chasenマニュアルの3章によると

3 茶筌ライブラリ
茶筌ライブラリlibchasen.a, libchasen.so を利用することで,茶筌のモジュールを他のプログラムに組
み込むことができる.ヘッダファイルとしてchasen.h をインクルードする.利用できるライブラリ関数・
数は以下の通りである

chasen_getopt_argv
   #include <chasen.h>
   int chasen_getopt_argv(char **argv, FILE *fp);
   extern int Cha_optind;
茶筌にオプションを渡す.もし茶筌の初期化が行われていなければ,初期化を行ってからオプションの
設定を行う.デフォルトのオプションのままでよければ,この関数を呼び出さずに以下の解析関数を呼
び出してもかまわない.
argv にはコマンドラインオプションとしてNULL で終わる文字列の配列を指定する.ただしargv[0] は
プログラムのファイル名である.オプション指定に誤りがあった場合,ファイル・ポインタfp にエラー
メッセージを出力する.fp がNULL のときは何も出力しない.
オプション指定に誤りがなければ0 を,誤りがあれば1 を返す.
外部変数Cha_optind には処理したオプション(argv[0] を含む) の数が格納される.
以下に使用例を示す.chawan というプログラムにおいて,‘-r /home/rikyu/chasenrc.proj -j’ とい
うオプションを茶筌に渡している.この関数の実行後Cha optind には4 が代入される.

   char *option[] = {"chawan", "-r", "/home/rikyu/.chasenrc.proj", "-j", NULL};
   chasen_getopt_argv(option, stderr);

chasen_fparse, chasen_sparse, chasen_fparse_tostr, chasen_sparse_tostr
    #include <chasen.h>
    int chasen_fparse(FILE *fp_in, *fp_out);
    int chasen_sparse(char *str_in, FILE *fp_out);
    char *chasen_fparse_tostr(FILE *fp_in);
    char *chasen_sparse_tostr(char *str_in);
もし茶筌の初期化が行われていなければ,初期化を行ってから形態素解析を行う.入力と出力がファイ
ルであるか文字列であるかによって,4 つの関数がある.
chasen_fparse(), chasen_fparse_tostr() はファイル・ポインタfp_in から読み込んだ文字列を解析
する.chasen_getopt_argv() で-j オプションを指定したときは,句点などを文の区切りとして解析を
行う.
    chasen_sparse(), chasen_sparse_tostr() は文字列str_in を解析する.
    chasen_fparse(), chasen_sparse() は解析結果をファイル・ポインタfp_out に出力する.返り値は0
    を返す.
    chasen_fparse_tostr(), chasen_sparse_tostr() は解析結果を茶筌内部で確保したメモリ領域に格納
    し,そのポインタを返す.この領域は,次にchasen_fparse_tostr(), chasen_sparse_tostr() を呼び
    出すまで有効である.

ということなので、簡単なサンプルプログラムを作ってみよう。

// Chasen Library Usage Test
#include <stdio.h>
#include <chasen.h>

main() {
  FILE *fp;
  char inbuf[80];
  char *option[] = {"chlibtest", "-i", "w", NULL};  // -iとwは別々〜なぜならオプションのパーザーは空白で切っているから。
  //char *option[] = {"chlibtest", "-j", "-i", "w", NULL};  //こんなこともあるだろう
  char *outp;

  fp = fopen("output.txt", "w");  //ファイル出力(chasen_sparse)を試すとき用

  if (chasen_getopt_argv(option, stderr)==1) {  // chasen初期化
    fprintf(stderr, "option error\n");
  }

  scanf("%s", inbuf);
  outp = chasen_sparse_tostr(inbuf); // chasen実行。inbufから読込みoutbufへ出力

 printf("%s\n", outp);
}

コンパイルは

gcc -L/usr/local/lib -lchasen -o chlibtest chlibtest.c

とした。chasenのライブラリ libchasen.soがあるのが/usr/local/libだから。

実行時にも、シェアドライブラリのパスが必要になる(実行時に動的にリンクするため)が、ここのような説明がある。http://www.linux.or.jp/JF/JFdocs/Program-Library-HOWTO/shared-libraries.html。 これの3.2節の4行目以下にこのように書かれている。

検索対象となるディレクトリのリストは、/etc/ld.so.conf ファイル内に記述されています。
Red Hat から派生しているディストリビューションの多くは、通常 /etc/ld.so.conf ファイル内に
/usr/local/lib を含めていません。私はこれをバグだと考えており、また、/usr/local/lib を
/etc/ld.so.conf に追加することは、Red Hat から派生しているシステム上で多くのプログラムを走らせるのに必要な、
共通の「修正」だと思っています。

FedoraはRedhatであるから、この問題があって、/usr/local/libを含めるようにした方がよさそうだ。ところで、/etc/ld.so.confを見るとinclude ld.so.conf.dになっているので、/etc/ld.so.conf.d/*.confファイルの1つとして追加するのがよさそうだ。

ここでは、/etc/ld.so.conf.d/userlocal.confを作成する。内容は1行のみ、/usr/local/libとする。作成したら、コマンドldconfigを実行し反映させる。

これで/usr/local/libも検索してくれるので、実行できる。(これをしないと、見つからない) 実行結果は

$ chlibtest               <<< コマンド入力
私は昨日学校へ行きました  <<< 入力データをstdinから。CRで入力1行終了
私      ワタシ  私      名詞-代名詞-一般
は      ハ      は      助詞-係助詞
昨日    キノウ  昨日    名詞-副詞可能
学校    ガッコウ        学校    名詞-一般
へ      ヘ      へ      助詞-格助詞-一般
行き    イキ    行く    動詞-自立       五段・カ行促音便        連用形
まし    マシ    ます    助動詞  特殊・マス      連用形
た      タ      た      助動詞  特殊・タ        基本形
EOS

chasenのラッパーを作ってみよう ver.-1

上の例題を参考にして、ラッパーを作ってみよう。(あくまで暫定版→改良版はこの次の節で)

#include "Python.h"
#include <chasen.h>

PyObject* pycha(PyObject* self, PyObject* args)
{
  char *option[] = {"pycha", "-j", "-i", "w", NULL};
  char *inbuf, *outp;
  if (!PyArg_ParseTuple(args, "s", &inbuf))  // stringでも&が必要である
     return NULL;

  if (chasen_getopt_argv(option, stderr)==1) { // chasen initialization
     return NULL;
  }
  outp = chasen_sparse_tostr(inbuf); // execute chasen. Read from inbuf. Output buf pointer is outp.

  return Py_BuildValue("s", outp);
}
//
static PyMethodDef pychamethods[] = {
  {"pycha", pycha, METH_VARARGS},
  {NULL},
};

void initpycha()
{
  Py_InitModule("pycha", pychamethods);
}

でよい。コンパイルは面倒なのでMakefileを準備

C = gcc
CFLAGS =
all : pychamodule.so

pycha.o : pycha.c
        $(CC) -fpic -L/usr/local/lib -I/usr/include/python -lchasen $(CFLAGS) -o pycha.o -c pycha.c

pychamodule.so : pycha.o
        $(CC) -shared $(CFLAGS) pycha.o -lchasen -o pychamodule.so

clean :
        rm *.o pychamodule.so

できたsoファイルは、面倒なので/usr/lib/python2.5/site-packages/の下に突っ込んでしまった。よくなかったかも知れない。

呼び出し側のpythonプログラムは

!/usr/bin/env python
# encoding: utf-8
# -*- coding: utf-8 -*-
##
import codecs
import pycha

### A magic for printing UTF-8 characters
sys.stdout = codecs.getwriter('utf_8')(sys.stdout)

s = u'私は昨日学校へ行きました'

t = pycha.pycha(s)

print t

となる。出力は、パイプを使ったときと同じ形の文字列であり、tuple化はされない。

tuple化した方が便利と思うが、それなりのコードを書く必要がある。やるとすればラッパー内でやるのだろうが、戻り値を戻す方法(実行時に個数の決まるタプルを返す方法)がよく分からない。

さしあたっては、python側で、出力文字列をパースして、タプルに組立てることにしようか。(かっこ悪いが)

ついでに実験。s = u'さいたさいたさくらがさいた' を与えた場合、

さい    サイ    さく    動詞-自立       五段・カ行イ音便        連用タ接続
た      タ      た      助動詞  特殊・タ        基本形
さい    サイ    さく    動詞-自立       五段・カ行イ音便        連用タ接続
た      タ      た      助動詞  特殊・タ        基本形
さくら  サクラ  さくら  名詞-一般
が      ガ      が      助詞-格助詞-一般
さい    サイ    さく    動詞-自立       五段・カ行イ音便        連用タ接続
た      タ      た      助動詞  特殊・タ        基本形
EOS

となって、結構賢いね。

chasenのラッパーを作ってみよう ver.0

上のテストは、出力が平坦な文字列で、情報を取り出すにはパースしなければならなかったので、格好が悪いし使いにくい。タプルで返す方法をもう少し勉強してみよう。

このページにあるAPIを使うと、タプル戻り値が意外に素直に出来た。まずタプルを返す簡単なサンプルを作った。

#include "Python.h"
extern int chcopy_body(char *);

PyObject* chcopy(PyObject* self, PyObject* args)
{
  char *inbuffer;
  char *result;
  int i;
  PyObject *pytuple, *pystring0, *pystring1, *pystring2;

  if (!PyArg_ParseTuple(args, "s", &inbuffer))
     return NULL;

  result = chcopy_body(inbuffer);
//  printf("chcopy_body: %s\n", result);
//  return Py_BuildValue("s", result);  // 普通はこうすれば戻り値を戻せる

// 以下はtupleを自分で作って返すテスト
  pystring0 = Py_BuildValue("s", result);  // 3つの文字列をタプルにして返す
  pystring1 = Py_BuildValue("s", "abc");
  pystring2 = Py_BuildValue("s", "def");
  if ((pytuple = PyTuple_New(3)) == NULL) {   // 新しいタプルを作る
    fprintf(stderr, "PyTuple_New failed\n");
    return NULL;
  }
  if (PyTuple_SetItem(pytuple, 0, pystring0) != 0) {  // タプル要素に値を設定
    fprintf(stderr, "PyTuple_SetItem failed\n");
    return NULL;
  }
  if (PyTuple_SetItem(pytuple, 1, pystring1) != 0) {
    fprintf(stderr, "PyTuple_SetItem failed\n");
    return NULL;
  }
  if (PyTuple_SetItem(pytuple, 2, pystring2) != 0) {
    fprintf(stderr, "PyTuple_SetItem failed\n");
    return NULL;
  }
  return pytuple;     // 出来たタプルを返す
}

static PyMethodDef chcopymethods[] = {
  {"chcopy", chcopy, METH_VARARGS},
  {NULL},
};

void initchcopy()
{
  Py_InitModule("chcopy", chcopymethods);
}

これを次のpythonから呼び出す。

s = u'私は昨日学校へ行きました'
t = chcopy.chcopy(s)
print t[0], t[1], t[2]

結果は

result, abc, def

ここまでを真似して、chasen出力をタプルとして返す仕組を作ってみる。

この時、ラッパー内に、chasen出力をパースするプログラムを作らなければならない。 いろいろな作り方がありそうだが、行を切り出し、そこから要素を切り出す時に、strtokを使ったら、しくじった。当たり前なのだが、strtokは内部にどこまで切り出したか(=残り)を覚えているので、入れ子にして使うことはできない。一応自前で(下記のmygettoken)で切り出すことにした。

#include "Python.h"
#include <string.h>
#include <chasen.h>

char *mygettoken(char *inbuf, char *token, char delimiter) {
  // inbuf is the buffer of the input.
  // token is the pointer to the return value which is the pointer to the extracted token.
  // delimiter is the delimiter character (single character).
  // mygettoken returns the new pointer to "inbuf", or nil if no token.
  int i;
  char *p;
  if ((inbuf == NULL)||(strlen(inbuf)==0)) return(NULL);
  if ((p = strchr(inbuf, delimiter)) != NULL) {
    *p = '\0';
    strncpy(token, inbuf, p-inbuf);
    token[p-inbuf] = '\0';
  } else {
    // Failed to find the delimiter.  Returns NULL to the token.
    if (strlen(inbuf)==0) {   // Input "inbuf" was an empty string
      strcpy(token, NULL);
      return(NULL);
    } else { // Doesn't end with "delimiter" but input possibly ends with \n
      strcpy(token, inbuf);
      return(inbuf+strlen(token));
    }
  }
  return(p+1);
}

PyObject* pycha(PyObject* self, PyObject* args)
{
  char *option[] = {"pycha", "-j", "-i", "w", NULL};
  char *inbuf, *outp;
  char line[256]; char *pline = line;
  char item[256]; char *pitem = item;
  int numlines, numitems;
  PyObject *pytuple, *pystring, *pyline, *pyresult;

  if (!PyArg_ParseTuple(args, "s", &inbuf))
     return NULL;

  if (chasen_getopt_argv(option, stderr)==1) { // chasen initialization
     return NULL;
  }
  outp = chasen_sparse_tostr(inbuf); // execute chasen. Read from inbuf. Output buf pointer is outp.

  numlines = 0;
  if ((pyresult = PyTuple_New(65535)) == NULL) {
     fprintf(stderr, "PyTuple_New failed\n");
     return NULL;
  }
  while ((outp = mygettoken(outp, pline, '\n')) != NULL) {
    numitems = 0;
    if ((pyline = PyTuple_New(32)) == NULL) {
       fprintf(stderr, "PyTuple_New failed\n");
       return NULL;
    }
    while ((pline = mygettoken(pline, pitem, '\t')) != NULL) {
      pystring = Py_BuildValue("s", pitem);
      if (PyTuple_SetItem(pyline, numitems, pystring) != 0) {
        fprintf(stderr, "PyTuple_SetItem failed\n");
        return NULL;
      }
      numitems++;
    }
    if (_PyTuple_Resize(&pyline, numitems) == -1) {
      fprintf(stderr, "PyTuple_Resize failed\n");
      return NULL;
    }
    if (PyTuple_SetItem(pyresult, numlines, pyline) != 0) {
      fprintf(stderr, "PyTuple_SetItem failed\n");
      return NULL;
    }
    numlines++;
    pline = line;
  }
  if (_PyTuple_Resize(&pyresult, numlines) == -1) {
    fprintf(stderr, "PyTuple_Resize failed\n");
    return NULL;
  }

  return pyresult;
}

static PyMethodDef pychamethods[] = {
  {"pycha", pycha, METH_VARARGS},
  {NULL},
};

void initpycha()
{
  Py_InitModule("pycha", pychamethods);
}

これのコンパイルは、前述の(出力が文字列の)ラッパーと同じでよい。これを駆動するPythonプログラム例は

#!/usr/bin/env python
# encoding: utf-8
# -*- coding: utf-8 -*-
##
## To run with Kanji properly, the Python default encoding should be set to utf-8.
##   This is done by including the file /usr/lib/python2.5/site-packages/sitecustomize.py
##   with such lines as
##     #!/usr/bin/env python
##     import sys
##     sys.setdefaultencoding('utf-8')
##   To check this is properly set, start python and issue
##     import sys
##     sys.getdefaultencoding()
##   which should reply with utf-8.
##   See http://python.matrix.jp/tips/string/encoding.html
##
import sys
import codecs
import pycha

### A magic for printing UTF-8 characters
sys.stdout = codecs.getwriter('utf_8')(sys.stdout)

s = u'私は昨日学校へ行きました'
##s = u'さいたさいたさくらがさいた'

t = pycha.pycha(s)

for x in t:
  for y in x:
     print y
  print      ## 改行だけ印字=空行作成

これの実行結果は、

私
ワタシ
私
名詞-代名詞-一般


は
ハ
は
助詞-係助詞


昨日
キノウ
昨日
名詞-副詞可能


学校
ガッコウ
学校
名詞-一般


へ
ヘ
へ
助詞-格助詞-一般


行き
イキ
行く
動詞-自立
五段・カ行促音便
連用形

まし
マシ
ます
助動詞
特殊・マス
連用形

た
タ
た
助動詞
特殊・タ
基本形

EOS

となる。

>>ノート/テキストマイニング/oregano
>>ノート/テキストマイニング/oregano3


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2009-02-28 (土) 18:55:44 (3036d)