在pyx/pxd文件中使用函数工厂生成C库的Cython函数包装器
我正在重新考虑如何将外部的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函数”,这是指_fun_factory
中的cpdef
。这是什么问题呢?我以为pyx
文件就像普通的Python文件一样。有没有其他方法可以让这个工作,而不是显而易见的从一个单独的Python脚本(比如setup.py
)动态生成pyx
文件?
我也很惊讶cython不让我这样做:
ctypedef ndarray[double_t, ndim=1] p_t
这样来清理代码。为什么这样不行呢?
我知道有自动的C -> cython
翻译工具,但我不太想依赖这些第三方工具。不过,如果你觉得有哪个工具已经可以投入生产使用,欢迎推荐。
2 个回答
经过进一步尝试,发现以下方法可以正常工作:
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 的预处理宏感兴趣?
pyx
文件和普通的 Python 文件不太一样,因为它们可以同时使用 C 和 Python 的函数,而且在使用 C 函数(比如 cdef
或 cpdef
)时有一些限制。首先,你不能在运行时动态生成 C 代码,这正是你代码中想要做的事情。由于 fun
实际上只是对参数进行类型检查后执行一些 Python 代码,你完全可以把它写成一个普通的 Python 函数:
def fun(P, x, y):
parse_args(P, x, y)
getattr(my_c_library, name)(&D)
return y
parse_args
也会进行相同的参数检查,所以你不会失去任何东西。(不过我不太确定 getattr
是否能在一个被 cimport
的 C 库上工作,你可能也需要 import
它。)
至于 ctypedef
,这可能是 Cython 中的某个限制或 bug,目前还没有人去修复它。