使用 cython 从多个 pyx 文件生成可执行文件

Din*_*uja 5 python cython cythonize

我正在尝试从 python 源文件创建一个 unix 可执行文件。

我有两个文件,p1.py并且p2.py

p1.py:-

from p2 import test_func 
print (test_func())
Run Code Online (Sandbox Code Playgroud)

p2.py:-

def test_func():
    return ('Test')
Run Code Online (Sandbox Code Playgroud)

现在,正如我们所看到的,p1.py依赖于p2.py. 我想通过将两个文件组合在一起来制作一个可执行文件。我正在使用 cython。

我将文件名分别更改为p1.pyxp2.pyx

现在,我可以使用 cython 使文件可执行,

cython p1.pyx --embed
Run Code Online (Sandbox Code Playgroud)

它将生成一个名为 .c 的 C 源文件p1.c。接下来我们可以使用 gcc 使其可执行,

gcc -Os -I /usr/include/python3.5m -o test p1.c -lpython3.5m -lpthread -lm -lutil -ldl 
Run Code Online (Sandbox Code Playgroud)

但是如何将两个文件合并为一个可执行文件呢?

ead*_*ead 3

您必须跳过一些循环才能使其正常工作。

首先,您必须意识到生成的可执行文件是一个非常薄的层,它只是将整个工作委托给(即从中调用函数)pythonX.Ym.so。调用时可以看到这种依赖关系

ldd test
...
libpythonX.Ym.so.1.0 => not found
...
Run Code Online (Sandbox Code Playgroud)

因此,要运行该程序,您需要显示LD_LIBRARY_PATH该程序的位置libpythonX.Ym.so或使用选项构建 exe --rpath,否则在动态加载程序启动时test将抛出类似于以下内容的错误

/test:加载共享库时出错:libpythonX.Ym.so.1.0:无法打开共享对象文件:没有这样的文件或目录

通用构建命令如下所示:

gcc -fPIC <other flags> -o test p1.c -I<path_python_include> -L<path_python_lib> -Wl,-rpath=<path_python_lib> -lpython3.6m <other_needed_libs>
Run Code Online (Sandbox Code Playgroud)

还可以针对 python 库的静态版本进行构建,从而消除对 libpythonX.Ym 的运行时依赖,例如参见此SO-post


生成的可执行文件的test行为与 python 解释器完全相同。这意味着现在test将会失败,因为它找不到该模块p2

一种简单的解决方案是对 p2 模块进行 cythonize 就地 ( cythonize p2.pyx -i):您将获得所需的行为 - 但是,您必须将生成的共享对象p2.sotest.

将两个扩展捆绑到一个可执行文件中很容易 - 只需将两个 cythonized c 文件传递​​给 gcc:

# creates p1.c:
cython --empbed p1.pyx
# creates p2.c:  
cython p2.pyx
gcc ... -o test p1.c p2.c ...
Run Code Online (Sandbox Code Playgroud)

但现在出现了一个新的(或旧的)问题:生成的test可执行文件无法再次找到 module ,因为python 路径上p2没有p2.pyno 。p2.so

关于这个问题有两个类似的SO问题,这里这里。在您的情况下,建议的解决方案有点矫枉过正,在这里,在将 p2 模块导入到 -file 中之前对其进行初始化就足够了p1.pyx

# making init-function from other modules accessible:
cdef extern  object PyInit_p2();

#init/load p2-module manually
PyInit_p2()  #Cython handles error, i.e. if NULL returned

# actually using already cached imported module
#          no search in python path needed
from p2 import test_func
print(test_func())
Run Code Online (Sandbox Code Playgroud)

如果模块之间存在循环依赖关系,则在导入模块之前调用模块的 init 函数(实际上该模块不会真正再次导入,只会在缓存中查找)也可以工作。例如,如果 modulep2导入 module p3,则 module 会p2依次导入。


警告:从 Cython 0.29 开始,Cython 对于 Python>=3.5 默认使用多阶段初始化,因此调用PyInit_p2是不够的(例如参见此 SO-post)。要关闭此多阶段初始化,-DCYTHON_PEP489_MULTI_PHASE_INIT=0应将其传递给 gcc 或类似于其他编译器。


注意:然而,即使在完成上述所有操作之后,嵌入式解释器也将需要其标准库(例如,参见此SO-post) - 要使其真正独立,还有很多工作要做!所以也许人们应该听听@DavidW的建议

“不要这样做”可能是绝大多数人的最佳解决方案。


警告:如果我们声明PyInit_p2()

from cpython cimport PyObject
cdef extern  PyObject *PyInit_p2();

PyInit_p2(); # TODO: error handling if NULL is returned
Run Code Online (Sandbox Code Playgroud)

Cython 将不再处理错误,这是我们的责任。代替

PyObject *__pyx_t_1 = NULL;
__pyx_t_1 = PyInit_p2(); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 4, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_1);
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
Run Code Online (Sandbox Code Playgroud)

为 -version 生成object,生成的代码变为:

(void)(PyInit_p2());
Run Code Online (Sandbox Code Playgroud)

即没有错误检查!

另一方面使用

cdef extern from *:
    """
    PyObject *PyInit_p2(void);
    """
    object PyInit_p2()
Run Code Online (Sandbox Code Playgroud)

不适用于 g++ - 必须添加extern C到声明中。