Cythonを使ってみる

python-simplexqueryでは、C&C++pythonのextensionを書いたけど、これ以上ベタでpython層変換を書くのはきついので、このあたりをサポートするツールを試すことにし、まずcythonを使ってみました。

cythonはpyxというpython言語の拡張言語(pyrex)のソースファイルを、cファイルに変換するpythonモジュールです。distutilsのコマンド拡張も入っているので、setup.pyに.cや.cpp同様直接.pyxを混ぜて入れることができ、cコードに変換後にコンパイルさせることも可能です。

setup.py

from distutils.core import setup, Extension
from Cython.Distutils import build_ext

setup(
  name = 'hello',
  cmdclass = {'build_ext': build_ext},
  ext_modules = [Extension("hello", ["chello.c", "hello.pyx"])]
)

ミソは、build_extをCythonのものに指定すること

chello.c

#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdio.h>
#include <string.h>
extern void * (* get_malloc(void))(size_t);

extern char *
say_hello(const char * name)
{
  char * hello = "Hello";
  int hellolen = strlen(hello);
  int namelen = strlen(name);
  int buflen = hellolen + 1 + namelen + 1;
  char * buf = get_malloc()(buflen);
  sprintf(buf, "%s %s", hello, name);
  return buf;
}

mallocpythonのものを使うことにします。ここでは、それを外で定義させるため、関数get_malloc()経由で取得させました。最初のdefineはcl.exeの警告を消すためのおまじない。

hello.pyx

cdef extern from "Python.h":
    void * PyMem_Malloc(size_t)
    void PyMem_Free(void *)
    pass

#cdef extern char * say_hello(char *)
# or
cdef extern:
    char * say_hello(char *)
    pass

ctypedef void * malloc_t(size_t)
cdef extern malloc_t * get_malloc():
    return PyMem_Malloc

# function returned func cdef requires arg name of its func type
cdef extern void (* get_free())(void * ptr):
    return &PyMem_Free

def say_hello_to(name):
    # convert py to c
    # cannot mix py exp in cdef exp without pyvars
    bname = name.encode("utf-8")
    cdef char * cname = bname

    cdef char * buf = say_hello(cname)
    # convert c to py
    ret = buf

    PyMem_Free(buf)
    return ret.decode("utf-8")

いくつかポイント

  • pyxはcにソースtoソースで変換される。
  • 型定義では、constや引数なし指定voidは書かない
  • typedefしないで関数型を返す関数の宣言部で、関数型に引数名が必要だったりする(型だけだとそれがobject型の変数とみなされる)。ctypedefではいらない
  • cdef文中でpythonの式は書けない。pythonの変数だけは使える
  • var = cvarでは、python objectが生成される。
  • ちなみにprintfのextern記述は、'cdef extern from "stdio.h": int printf(char *, ...)'

実行

普通のと同じで、python setup.py build でビルドできます。できたモジュールはcythonライブラリを必要としません。

python3対応

cythonの生成cファイルは、python2でもpython3でも対応している。

ただし、Cython-0.12.1のCython/Compiler/Main.pyがpython3非互換なのでsetup.py build時に修正する必要がある。

  • build時のエラー部分をprint(msg)、raise Excep(msg)、except Excep as ex: などにする程度。2to3でいける?