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

Python 使用cython从多个pyx文件生成可执行文件,python,cython,cythonize,Python,Cython,Cythonize,我正在尝试从python源文件生成一个unix可执行文件 我有两个文件,p1.py和p2.py p1.py:- from p2 import test_func print (test_func()) p2.py:- def test_func(): return ('Test') 现在,我们可以看到,p1.py依赖于p2.py。我想通过组合两个文件来生成一个可执行文件。我在用cython 我将文件名分别更改为p1.pyx和p2.pyx 现在,我可以使用cython使文件可执行 c

我正在尝试从python源文件生成一个unix可执行文件

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

p1.py:-

from p2 import test_func 
print (test_func())
p2.py:-

def test_func():
    return ('Test')
现在,我们可以看到,
p1.py
依赖于
p2.py
。我想通过组合两个文件来生成一个可执行文件。我在用cython

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

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

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

gcc -Os -I /usr/include/python3.5m -o test p1.c -lpython3.5m -lpthread -lm -lutil -ldl 

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

要使其工作,您必须跳转一些循环

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

ldd test
...
libpythonX.Ym.so.1.0 => not found
...
因此,要运行该程序,您需要将
LD_LIBRARY_PATH
显示到
libpythonX.Ym.So
的位置,或者使用
--rpath
选项构建exe,否则在启动
测试时,动态加载程序将抛出类似于

/测试:加载共享库时出错: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>
但是现在出现了一个新的(或旧的)问题:生成的
test
-可执行文件无法再次找到模块
p2
,因为python路径上没有
p2.py
p2.so

关于这个问题有两个相似的SO问题,和。在您的情况下,建议的解决方案有点过头了,在将p2模块导入到
p1.pyx
-文件之前初始化p2模块就足够了,以使其工作:

# 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())
如果模块之间存在循环依赖关系,则在导入模块之前调用模块的init函数(实际上,模块不会再次真正导入,只会在缓存中查找)也会起作用。例如,如果模块
p2
导入模块
p3
,则依次导入
p2


警告:由于Cython 0.29,Cython在Python>=3.5的默认情况下使用多阶段初始化,因此调用
PyInit_p2
是不够的(参见示例)。要关闭此多阶段初始化,应将DCYTHON_PEP489_multi_phase_INIT=0
传递给gcc或类似的其他编译器


注意:然而,即使在完成上述所有操作之后,嵌入式解释器仍将需要其标准库(参见此示例)-要使其真正独立,还有很多工作要做!因此,也许我们应该注意:

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


警告:如果我们声明
PyInit\u p2()

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

PyInit_p2(); # TODO: error handling if NULL is returned
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;
对象
-版本生成,生成的代码仅为:

(void)(PyInit_p2());
i、 无错误检查

另一方面,使用

cdef extern from *:
    """
    PyObject *PyInit_p2(void);
    """
    object PyInit_p2()

不能与g++一起使用-必须在声明中添加
extern C

人们很想这样做,因为对于最简单的情况(一个模块,没有依赖项),这是相当容易做到的。它正在处理下一个最简单的情况(两个模块,您完全可以控制,没有依赖关系)

一般来说,Python程序将依赖于一系列外部模块。Python附带了一个很大的标准库,大多数程序都在一定程度上使用它。有很多第三方库用于数学、GUI和web框架。即使通过库跟踪这些依赖项并确定需要构建的内容也很复杂,PyInstaller之类的工具也会尝试这样做,但并不是100%可靠

在编译所有这些Python模块时,您可能会遇到一些Cython不兼容/bug。它通常相当不错,但与内省之类的特性有矛盾,因此不太可能有一个大型项目能够干净、完整地编译

除此之外,许多模块都是用C编写的编译模块,或者使用SWIG、F2Py、Cython、boost python等工具编写的。。这些编译后的模块可能有自己独特的特性,这使得它们很难链接到一个大blob中

总之,这是可能的,但对于非琐碎的程序来说,无论它看起来多么吸引人,这都不是一个好主意。像PyInstaller和Py2Exe这样使用更简单方法(将所有内容捆绑到一个巨大的zip文件中)的工具更适合此任务(即使这样,它们也很难做到真正健壮)


注意:发布此答案的目的是使此问题成为此问题的标准副本。虽然一个说明如何实现的答案很有用,“不要这样做”对于绝大多数人来说可能是最好的解决方案。

我认为“不要这样做”是一个非常可靠的建议,即使我理解拥有一个真正独立的嵌入式python解释器的吸引力,并不时抵制这样做的冲动。但不确定Cython是否是处理.py文件的最佳工具:冻结模块似乎是一种更好/更稳健的策略。
cdef extern from *:
    """
    PyObject *PyInit_p2(void);
    """
    object PyInit_p2()