使用pyx / pxd文件中的函数工厂为C库生成cython函数包装器

时间:2013-03-05 16:10:48

标签: python numpy cython python-c-extension

我正在重新评估将外部C库包装到Python中的不同方法。我很久以前就选择使用普通的Python C API,它快速,简单,独立,并且正如我所想的那样,面向未来。然后我偶然发现了PyPy,它显然不打算支持CPython API,但未来可能会成为一个有趣的选择......我正在寻找更高级别的切入点。 ctypes很慢,所以现在回到cython,这似乎是为了支持PyPy。

我的库有很多具有相同签名的函数,所以我广泛使用C预处理器宏来生成Python模块。我认为这会在cython中变得更加舒适,因为我可以访问整个Python语言。但是,我在为函数包装器编写工厂时遇到了麻烦:

import cython
from numpy cimport ndarray, double_t

cimport my_c_library

cdef my_c_library.data D

ctypedef double_t DTYPE_t

cdef parse_args(ndarray[DTYPE_t] P, ndarray[DTYPE_t] x, ndarray[DTYPE_t] y):
    D.n = P.size
    D.m = x.size
    D.P = <double*> P.data
    D.x = <double*> x.data
    D.y = <double*> y.data

def _fun_factory(name):
    cpdef fun(ndarray[DTYPE_t] P, ndarray[DTYPE_t] x, ndarray[DTYPE_t] y):
        parse_args(P, x, y)
        getattr(my_c_library, name)(&D)
        return y
    return fun

fun1 = _fun_factory('fun1')
fun2 = _fun_factory('fun2')
# ... many more function definitions ...

cython编译器抱怨:“此处不允许使用C函数定义”,引用cpdef内的_fun_factory。这里有什么问题?我认为pyx文件就像普通的python文件一样。有没有办法让这个工作,除了明显从单独的python脚本动态生成pyx文件,如setup.py

我也很惊讶cython不允许我这样做:

ctypedef ndarray[double_t, ndim=1] p_t

清理代码。为什么这不起作用?

我知道那里有自动C -> cython翻译,但我不愿意依赖这些第三方工具。但如果您认为可以投入生产,请随时提出建议。

2 个答案:

答案 0 :(得分:3)

pyx文件与Python文件不同,您可以匹配C和Python函数,并且对使用C(cdef或{{1}可以执行的操作存在一些限制})功能。首先,您无法在运行时动态生成C代码,这正是您的代码尝试执行的操作。由于cpdef实际上只是在对其参数进行类型检查后执行某些Python代码,因此您可以将其作为常规Python函数:

fun

def fun(P, x, y): parse_args(P, x, y) getattr(my_c_library, name)(&D) return y 将执行相同的参数检查,因此您不会丢失任何内容。 (我不确定parse_args是否适用于getattr'的C库。您可能也想要cimport。)

对于import,这可能是Cython中的一些限制/错误,没有人可以解决这个问题。

答案 1 :(得分:0)

在玩了一些之后,以下似乎有效:

def _fun_factory(fun_wrap):
    def fun(P, x, y):
        parse_args(P, x, y)
        fun_wrap()
        return y
    return fun

def _fun1(): my_c_library.fun1(&D)
def _fun2(): my_c_library.fun2(&D)
# ... many more ...

fun1 = _fun_factory(_fun1)
fun2 = _fun_factory(_fun2)
# ... many more...

因此,似乎不可能对像my_c_library.fun1(&D)这样的表达式使用任何Python操作,这显然需要按原样输入。当已经生成第一组Python包装器时,工厂只能在第二次传递时使用。这并不比显而易见的更优雅:

cpdef fun1(ndarray[DTYPE_t] P, ndarray[DTYPE_t] x, ndarray[DTYPE_t] y):
    parse_args(P, x, y)
    my_c_function.fun1(&D)
    return y

# ... many more ...

在这里,可以毫无问题地使用cpdef。所以我要采用复制粘贴方法......未来还有人对Cython的预处理器宏感兴趣吗?