通过 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_AppendInittabimpl_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 库的主要内容,如果未能解决你的问题,请参考以下文章

CYT1000B长运通线性LED方案

如何从 Matlab 调用具有多个输出的 C++ 函数?

如何在 C++ 中运行具有仅调用线程的函数的类的多个对象?

在Cython中使用pyqt类(.pyx文件)

将 cython 函数与 cython 方法传递给 scipy.integrate

关于制作 Python .pyx 文件的教程的问题