pymmseg-cpp: rmmseg-cpp with Python interface

August 14, 2008 – 1:16 pm

以前为了提高 RMMSeg 的性能,我写了 rmmseg-cpp ,根据 JavaEye 一段时间的使用情况来看,似乎挺稳定的,性能也很不错。其实 rmmseg-cpp 虽然名字里面有一个 “r” 字,但是算法核心完全是用 C++ 做的,最后才加了一层 Ruby 的接口包装。正巧最近我又两个用 Python 的项目都要用到中文分词,便决定给 rmmseg-cpp 加一个 Python 的接口。

正如我在 On the Rubinius FFI 一文中描述的一样,做到 Native 模块的接口有两种方法,一种是我在 rmmseg-cpp 中用的写 C 代码来粘合二者。另一种则是直接用语言提供的 FFI 接口,写 Python 代码来粘合。也可以算是各有各的好处吧,不过这里用 FFI 有一个很吸引我的地方就是粘合代码不用进行编译。如果用 C 来写粘合代码的话,需要用编译 Python 解释器相同的编译环境下加上 Python 的源代码(至少是头文件)一起编译,在 Linux 下倒是挺方便的,但是在 Windows 下可以说是相当麻烦,这似乎也是 rmmseg-cpp 一直不支持 Windows 平台的原因。但是如果用 FFI 来写粘合代码的话,就只需要单独编译原本的 Native 模块。编译一个标准的 C/C++ 模块和编译一个依赖于 Python/Ruby 源代码/头文件的模块是不一样的,这样的任务即使在 Windows 下也可以很方便地完成。

于是我就理所当然地选择了用 Python 的 FFI : ctypes 来做。首先为原来的 C++ 模块做一层简单的 C 浅包装,这是因为 C++ 的符号在二进制模块中的命名没有规范,函数名经过编译器 mingling 之后的结果虽然看起来都是像一堆乱码,但是不同的编译器会是不同的“乱码”,根本不通用,所以要在 shared library 中导出的话,还是得用 C 来做。几乎都是类似于这样的 trivial 的包装:

rmmseg::Algorithm *mmseg_algor_create(const char *text, int len)
{
    return new rmmseg::Algorithm(text, len);
}

然后将它编译为一个 shared library (Linux 下的 so 或者 Windows 下的 dll),在 Python 中通过 ctypes 加载进来。粘合代码虽然是 Python 代码,但是也并不简洁,因为 Python 并不会读取 C 的头文件,所以还要手工定义函数的 prototype ,类似于这样:

mmseg.mmseg_algor_create.argtypes = [c_char_p, c_int]
mmseg.mmseg_algor_create.restype  = c_void_p

这里我们并不关心创建出来的 rmmseg::Algorithm 对象是什么样子的,我们只是要将它作为参数再传回模块中而已,所以只要把它当作一个 void * (即 c_void_p)看待就可以了。

定义好函数原型之后,函数就可以直接调用了,这都是 ctypes 的功劳,最后,再把这些函数包装到一个更加 Python 的接口中:

class Algorithm(object):
    def __init__(self, text):
        """\
        Create an Algorithm instance to segment text.
        """
        self.algor = mmseg.mmseg_algor_create(text, len(text))
        self.destroied = False
 
    def __iter__(self):
        """\
        Iterate through all tokens. Note the iteration has
        side-effect: an Algorithm object can only be iterated
        once.
        """
        while True:
            tk = self.next_token()
            if tk is None:
                raise StopIteration
            yield tk
 
    # ... snip ...

就万事大吉了!然后就可以在 Python 代码中使用了:

from pymmseg import mmseg
 
mmseg.dict_load_defaults()
text = # ...
algor = mmseg.Algorithm(text)
for tok in algor:
    print '%s [%d..%d]' % (tok.text, tok.start, tok.end)

pymmseg-cpp 的代码仍然放在 github 上,目前我发布了一个 source tarball 和一个 Linux i386 的预编译包,暂时放在这里

Windows 下的预编译包虽然理论上来说是很容易做的,但是我测试的时候却发现 ctypes 会报一些莫名其妙的错误,比如说我调用函数的时候参数数目不对之类的。我将原本的 mmseg-cpp 的代码编译为 exe 测试的时候似乎没有问题,我想原因也许出在 ctypes 或者是和 Python 接口的地方吧。待我抽时间来好好调试一下看看能否找出原因。 :)

  1. 19 Responses to “pymmseg-cpp: rmmseg-cpp with Python interface”

  2. Good news! 看到rmmseg-cpp终于解决掉Leopard下的引用问题了。下午试一下。辛苦了。

    By ashchan on Aug 14, 2008

  3. @ashchan,
    那是别人打的一个 patch ,我昨天无意看到的,就加到仓库里去了。因为自己也没有 Mac ,无法测试。既然现在没有问题了,那么我回头发布一个新版本去。 :)

    By pluskid on Aug 14, 2008

  4. 发现一些错别字,赶快改过来吧,看来是fcitx的功劳….

    并不简[介], 要[讲]它 …

    By quark on Aug 14, 2008

  5. @quark,
    *^_^* 多谢!不过你这个评论应该是发在前一篇文章那里吧?

    By pluskid on Aug 15, 2008

  6. …抢偶生意。。。。
    其实全用C写不就没有name mangling的问题了?

    By Mike on Aug 15, 2008

  7. @pluskid,
    就是这篇文章啊?

    By quark on Aug 15, 2008

  8. @quark,
    这篇文章里哪里有“简介”或“简洁”这个词啊? ^_^

    By pluskid on Aug 15, 2008

  9. @Mike,
    代码以前用 C++ 写好了的,何必再用 C 重写呢?

    By pluskid on Aug 15, 2008

  10. @pluskid,
    已经改过来了嘛… ~

    By quark on Aug 15, 2008

  11. @quark,
    寒……难道是有错乱了?怎么又变成这篇文章了? -.-bb

    By pluskid on Aug 15, 2008

  12. pluskid你好,有时分词会出现段错误,用catchsegv跟踪结果如下:

    Backtrace:
    /lib/libSegFault.so[0xb8080100]
    [0xb8085400]
    /xapian/pymmseg/mmseg-cpp/mmseg.so(mmseg_next_token+0x21)[0xb7aac3c1]
    /usr/lib/python2.5/site-packages/_ctypes.so(ffi_call_SYSV+0x17)[0xb7853467]
    /usr/lib/python2.5/site-packages/_ctypes.so(ffi_call+0x86)[0xb78532e6]
    /usr/lib/python2.5/site-packages/_ctypes.so(_CallProc+0x2e4)[0xb784e5e4]
    /usr/lib/python2.5/site-packages/_ctypes.so[0xb784902c]
    /usr/bin/python2.5(PyObject_Call+0x27)[0x805d897]
    /usr/bin/python2.5(PyEval_EvalFrameEx+0x371d)[0x80ccd4d]
    /usr/bin/python2.5(PyEval_EvalFrameEx+0x604d)[0x80cf67d]
    /usr/bin/python2.5[0x81151d6]
    /usr/bin/python2.5(PyEval_EvalFrameEx+0x919)[0x80c9f49]
    /usr/bin/python2.5(PyEval_EvalFrameEx+0x604d)[0x80cf67d]
    /usr/bin/python2.5(PyEval_EvalFrameEx+0x604d)[0x80cf67d]
    /usr/bin/python2.5(PyEval_EvalCodeEx+0x685)[0x80cfea5]
    /usr/bin/python2.5(PyEval_EvalFrameEx+0x51a4)[0x80ce7d4]
    /usr/bin/python2.5(PyEval_EvalCodeEx+0x685)[0x80cfea5]
    /usr/bin/python2.5(PyEval_EvalFrameEx+0x51a4)[0x80ce7d4]
    /usr/bin/python2.5(PyEval_EvalCodeEx+0x685)[0x80cfea5]
    /usr/bin/python2.5(PyEval_EvalFrameEx+0x51a4)[0x80ce7d4]
    /usr/bin/python2.5(PyEval_EvalCodeEx+0x685)[0x80cfea5]
    /usr/bin/python2.5(PyEval_EvalFrameEx+0x51a4)[0x80ce7d4]
    /usr/bin/python2.5(PyEval_EvalCodeEx+0x685)[0x80cfea5]
    /usr/bin/python2.5(PyEval_EvalCode+0x57)[0x80d00b7]
    /usr/bin/python2.5(PyRun_FileExFlags+0xef)[0x80edaef]
    /usr/bin/python2.5(PyRun_SimpleFileExFlags+0x1a2)[0x80eddc2]
    /usr/bin/python2.5(Py_Main+0xc57)[0x8059617]
    /usr/bin/python2.5(main+0x22)[0x8058992]
    /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7ee3775]
    /usr/bin/python2.5[0x80588d1]

    By youngking on Jun 7, 2009

  13. 咦,很奇怪呀,请问你能提供可以重现这样错误的数据吗?

    By pluskid on Jun 16, 2009

  14. 我也想添加中文分词,但是我的机器是linux_64的,请问是否可以使用?我make时候的提示错误:/usr/bin/ld: memory.o: relocation R_X86_64_32 against `__gxx_personality_v0′ can not be used when making a shared object; recompile with -fPIC
    memory.o: could not read symbols: Bad value
    collect2: ld returned 1 exit status

    By 耿志峰 on Jul 20, 2009

  15. 64 位的 Linux 应该是可以的,我记得我好像曾经在一个 Ubuntu AMD64 上测试过,是可以用的。不知道你用的是什么平台?看那个错误信息好像是编译的时候没有加上 -fPIC 选项,但是我在 build.py 里确实加了这个选项了啊。你看它在编译的时候有显示编译命令是正常的吗?

    By pluskid on Jul 20, 2009

  16. 我按照上面的mmseg的调用 为什么显示出来有问题
    全是乱码 比如鏄ㄥぉ [3..9]之类的

    By supernova on Nov 21, 2009

  17. @supernova,
    请看一下你的字符串是不是 utf-8 编码的。mmseg 只支持 utf-8 。 :)

    By pluskid on Nov 21, 2009

  1. 3 Trackback(s)

  2. Dec 31, 2009: » 中文分词的整理 Geoinformatics
  3. Sep 25, 2010: 中文分词的整理 | 彭旭赣州seo优化
  4. Dec 14, 2010: 主要中文汉字分词算法速度效率收集比较 飞狐itweb-IT知识家园

Post a Comment