使用cython从多个pyx文件制作可执行文件

时间:2018-10-24 01:28:21

标签: python cython cythonize

我正在尝试从我的python源文件中制作一个unix可执行文件。

我有两个文件p1.pyp2.py

p1.py:-

from p2 import test_func 
print (test_func())

p2.py:-

def test_func():
    return ('Test')

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

我将文件名分别更改为p1.pyxp2.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 

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

2 个答案:

答案 0 :(得分:2)

要使其正常运行,您必须跳过一些循环。

首先,您必须知道生成的可执行文件是一个非常薄的层,它仅将整个工作委托给pythonX.Ym.so(即从其中调用函数)。调用

时,您会看到此依赖性
ldd test
...
libpythonX.Ym.so.1.0 => not found
...

因此,要运行该程序,您需要将LD_LIBRARY_PATH显示在libpythonX.Ym.so的位置,或者使用--rpath选项构建exe,否则在启动时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>

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

一个简单的解决方案,其中就地对p2-module(cythonize p2.pyx -i)进行了囊囊化处理,您将获得所需的行为-但是,您必须将生成的共享对象p2.so与{{1 }}。

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

test

但是现在出现了一个新的(或旧的)问题:产生的# creates p1.c: cython --empbed p1.pyx # creates p2.c: cython p2.pyx gcc ... -o test p1.c p2.c ... -可执行文件无法再次找到模块test,因为没有p2也没有{{1 }}上的python-path。

关于此问题,有两个类似的SO问题,herehere。在您的情况下,建议的解决方案有点过头了,在将p2模块导入到p2.py文件中使其生效之前,只需对其进行初始化即可:

p2.so

如果模块之间存在循环依赖性,则在导入模块之前调用模块的init函数(实际上不会真正第二次导入模块,仅在高速缓存中查找)。例如,如果模块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()) ,该模块依次导入p2

自Cython 0.29起,Cython在默认情况下针对Python> = 3.5使用多阶段初始化,因此仅调用p3是不够的(例如参见this SO-post)。要关闭此多阶段初始化,p2应该传递给gcc或类似的其他编译器。


警告:如果我们将PyInit_p2声明为

-DCYTHON_PEP489_MULTI_PHASE_INIT=0

Cython将不再处理错误以及我们的责任。代替

PyInit_p2()

from cpython cimport PyObject cdef extern PyObject *PyInit_p2(); PyInit_p2(); # TODO: error handling if NULL is returned 版生产,生成的代码变为:

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;

即没有错误检查!

另一方面使用

object

不适用于g ++-必须在声明中添加(void)(PyInit_p2());

答案 1 :(得分:1)

人们很想这样做,因为在最简单的情况下(一个模块,没有依赖项)这很容易做到。 @ead's answer is good but honestly pretty fiddly,它正在处理下一个最简单的情况(您可以完全控制两个模块,没有依赖性)。

通常,Python程序将取决于一系列外部模块。 Python带有一个大型标准库,大多数程序在一定程度上都使用该库。有大量用于数学,GUI,Web框架的第三方库。甚至通过库来跟踪这些依赖关系并确定您需要构建的内容也很复杂,并且PyInstaller之类的工具会尝试这种方法,但并非100%可靠。

在编译所有这些Python模块时,您可能会遇到一些Cython不兼容/错误。通常来说,它还不错,但与自省之类的功能很不兼容,因此,大型项目不太可能完全干净地编译。

最重要的是,这些模块中的许多模块都是用C或使用诸如SWIG,F2Py,Cython,boost-python等工具编写的已编译模块。这些已编译模块可能具有自己独特的特质,这使它们很难链接在一起成为一个大Blob。

总而言之,这是有可能的,但是对于非平凡的程序这不是一个好主意,尽管它看起来很有吸引力。像PyInstaller和Py2Exe这样的工具使用更简单的方法(将所有内容捆绑到一个巨大的zip文件中)更适合于此任务(即使那样,它们也很难变得非常强大)。


注意发布此答案的目的是使该问题成为该问题的规范重复。显示如何完成的答案很有用,但“不要这样做”可能是绝大多数人的最佳解决方案。