通过 c++ 调用具有多个 pyx 文件的 cython 库
Posted
技术标签:
【中文标题】通过 c++ 调用具有多个 pyx 文件的 cython 库【英文标题】:Calling a cython library with multiple pyx files through c++ 【发布时间】:2018-10-04 11:54:44 【问题描述】:我有一个要从 c++ 应用程序调用的 python 项目。我想将所有 python 源捆绑在一个共享库中,并将 c++ 应用程序链接到该库。现在我的 cython setup.py 为每个 python 源创建一个 *.so
,这非常不方便。
这是setup.py
文件:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
sourcefiles = ['project_interface.pyx', 'impl_file1.pyx']
setup(
ext_modules = cythonize(sourcefiles)
)
project_interface.pyx:
# distutils: language = c++
import impl_file1
cdef public void HelloWorld():
print "Calling Hello World"
impl_file1.helloworld_func()
impl_file1.pyx:
def helloworld_func():
print 'Hello World'
我尝试修改 setup.py 以将所有 python 代码捆绑在一个库中,如下所示:
setup(
ext_modules = cythonize([Extension("my_project", sourcefiles, language='c++')])
)
不幸的是,在执行void HelloWorld()
时,应用程序无法再归档 impl_file1。我明白了:
Calling Hello World
NameError: name 'impl_file1' is not defined
Exception NameError: "name 'impl_file1' is not defined" in 'project_interface.HelloWorld' ignored
驱动这个的 c++ 程序是:
#include <Python.h>
#include "project_interface.h"
int main(int argc, const char** argv)
Py_Initialize();
initproject_interface();
HelloWorld();
Py_Finalize();
return 0;
当使用多个*.so
文件进行编译时,此应用程序可以正常工作。
无论哪种情况,编译都非常简单:
python setup.py build_ext --inplace
mv my_project.so libmy_project.so
g++ main.cpp -o main `python2-config --cflags --ldflags` -L. -lmy_project
有没有办法让单一共享库解决方案发挥作用?
【问题讨论】:
【参考方案1】:关于将多个 Cython 模块捆绑在一起(例如 1、2)有许多类似的问题,但实际上并不可行,因为 Python 使用文件路径来处理模块。但是,这个问题并不完全相同,因为您是从 C++ 调用它,这为您提供了一个额外的选择。
您需要使用 Python 的 C API 函数 PyImport_AppendInittab
将 impl_file1
视为内置模块,因此它不会搜索要导入的文件的路径。首先提供一个导入函数的声明(因为你不会从你的头文件中得到它):
extern "C"
// PyObject* PyInit_impl_file1(); // Python 3
void initimpl_file1(); // Python 2
然后,在main
中,在Py_Initialize
之前,添加:
PyImport_AppendInittab("impl_file1", initimpl_file1); // change the name for Python 3
【讨论】:
您的回答似乎是“官方”方式:cython.readthedocs.io/en/latest/src/userguide/…。但它真的有效吗?如果我用 Python3 尝试它,我会得到ImportError: 'xxxx' is not a built-in module
,因为这里的健全性检查 github.com/python/cpython/blob/…。我将模块名称添加到 sys.builtin_module_names
以解决它,不确定这是最好的方法。
@ead 我确实用 Python 3(在 Linux 上)测试了我的答案,所以我可以确认它对我有用。 (虽然我没有用 Python 2 测试过,只是因为我没有安装头文件等)。您链接到的代码行看起来已经有 10 年历史了,所以我无法想象会发生这种情况,因为您运行的版本与我不同
我已经添加了我的最小示例作为答案 (***.com/a/52698794/5769463) 会很有趣知道为什么会有差异。
我注意到在 impl_file1.pyx 中添加一个 cdef public void func()
函数将生成一个包含 initimpl_file1() 调用的 impl_file1.h 标头,从而减少所需的胶带数量【参考方案2】:
对我来说(在稍微不同的情况下,但我没想到会有所不同)@DavidW 的解决方案需要一些调整。这是我的设置:
foo.pyx:
cdef extern from "Python.h":
int PyImport_AppendInittab(const char *name, object (*initfunc)())
cdef extern from *:
"""
PyObject *PyInit_bar(void);
"""
object PyInit_bar()
PyImport_AppendInittab("bar", PyInit_bar)
import bar # HERE The error happens
bar.pyx:
print("bar imported")
setup.py:
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
sourcefiles = ['foo.pyx', 'bar.pyx']
setup(
ext_modules = cythonize([Extension("foo", sourcefiles)])
)
现在使用python setup.py build_ext -i
构建后,会出现错误:
import foo
ImportError: 'bar' is not a built-in module
来自here。要解决此问题,我必须将名称“bar”添加到 sys.builtin_module_names
:
...
import sys
sys.builtin_module_names = list(sys.builtin_module_names)+["bar"]
import bar
【讨论】:
如果我理解正确,您将 PyImport_AppendInitTab 调用移至 foo.pyx。在这种情况下, foo 客户端不必手动调用它。 我相信 AppendInitTab 需要在 Python 初始化之前被调用,这意味着你不能真正把它放在 Cython 模块中。因此,我的解决方案适用于将 Python 嵌入到 C(++) 中,但不是将 Cython 模块捆绑在一起的通用方法。 @Eric 这正是我试图实现的目标,但似乎并不是那么直截了当。 我找到了一些解决方法。您可以将所有PyImport_AppendInittab
调用放在一个cpp 文件中,然后将该文件添加到setup.py 中的sourcefiles
。然后,您调用该函数一次以注册所有内容。这个想法是客户端根本不需要知道生成的库的细节
@Eric 可能更好:您可以通过调用您的 common-init-function 来初始化一个虚拟全局变量(显然,副作用是您感兴趣的)。然后这个函数会在 main 之前被自动调用(只要 so 被加载),而不需要显式调用它。以上是关于通过 c++ 调用具有多个 pyx 文件的 cython 库的主要内容,如果未能解决你的问题,请参考以下文章