简体   繁体   English

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

[英]Make executable file from multiple pyx files using cython

I am trying to make one unix executable file from my python source files.我正在尝试从我的 python 源文件制作一个 unix 可执行文件。

I have two file, p1.py and p2.py我有两个文件, p1.pyp2.py

p1.py :- p1.py :-

from p2 import test_func 
print (test_func())

p2.py :- p2.py :-

def test_func():
    return ('Test')

Now, as we can see p1.py is dependent on p2.py .现在,我们可以看到p1.py依赖于p2.py I want to make an executable file by combining two files together.我想通过将两个文件组合在一起来制作一个可执行文件。 I am using cython.我正在使用赛通。

I changed the file names to p1.pyx and p2.pyx respectively.我将文件名分别更改为p1.pyxp2.pyx

Now, I can make file executable by using cython,现在,我可以使用 cython 使文件可执行,

cython p1.pyx --embed

It will generate a C source file called p1.c .它将生成一个名为p1.c的 C 源文件。 Next we can use gcc to make it executable,接下来我们可以使用 gcc 使其可执行,

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

But how to combine two files into one executable ?但是如何将两个文件合并为一个可执行文件呢?

There are some loops you have to jump through to make it work.您必须跳过一些循环才能使其工作。

First, you must be aware that the resulting executable is a very slim layer which just delegates the whole work to (ie calls functions from) pythonX.Ym.so .首先,您必须意识到生成的可执行文件是一个非常薄的层,它只是将整个工作委托给(即从中调用函数) pythonX.Ym.so You can see this dependency when calling调用时可以看到这个依赖

ldd test
...
libpythonX.Ym.so.1.0 => not found
...

So, to run the program you either need to have the LD_LIBRARY_PATH showing to the location of the libpythonX.Ym.so or build the exe with --rpath option, otherwise at the start-up of test dynamic loader will throw an error similar to因此,要运行该程序,您要么需要将LD_LIBRARY_PATH显示到libpythonX.Ym.so的位置,要么使用--rpath选项构建 exe,否则在test动态加载器启动时会抛出类似于

/test: error while loading shared libraries: libpythonX.Ym.so.1.0: cannot open shared object file: No such file or directory /test:加载共享库时出错:libpythonX.Ym.so.1.0:无法打开共享对象文件:没有这样的文件或目录

The generic build command would look like following:通用构建命令如下所示:

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>

It is also possible to build against static version of the python-library, thus eliminating run time dependency on the libpythonX.Ym, see for example this SO-post .也可以针对 python 库的静态版本进行构建,从而消除对 libpythonX.Ym 的运行时依赖,例如参见这个SO-post


The resulting executable test behaves exactly the same as if it were a python-interpreter.生成的可执行test行为与 Python 解释器完全相同。 This means that now, test will fail because it will not find the module p2 .这意味着现在test将失败,因为它找不到模块p2

One simple solution were to cythonize the p2-module inplace ( cythonize p2.pyx -i ): you would get the desired behavior - however, you would have to distribute the resulting shared-object p2.so along with test .一个简单的解决方案是就地对 p2 模块进行 cythonize ( cythonize p2.pyx -i ):您将获得所需的行为 - 但是,您必须将生成的共享对象p2.sotest一起分发。

It is easy to bundle both extension into one executable - just pass both cythonized c-files to gcc:很容易将两个扩展捆绑到一个可执行文件中 - 只需将两个 cythonized c 文件传递​​给 gcc:

# creates p1.c:
cython --empbed p1.pyx
# creates p2.c:  
cython p2.pyx
gcc ... -o test p1.c p2.c ...

But now a new (or old) problem arises: the resulting test -executable cannot once again find the module p2 , because there is no p2.py and no p2.so on the python-path.但是现在出现了一个新的(或旧的)问题:生成的test -executable 无法再次找到模块p2 ,因为在 python 路径上没有p2.pyp2.so

There are two similar SO questions about this problem, here and here .关于这个问题,这里这里有两个类似的 SO 问题。 In your case the proposed solutions are kind of overkill, here it is enough to initialize the p2 module before it gets imported in the p1.pyx -file to make it work:在您的情况下,建议的解决方案有点矫枉过正,在这里在 p2 模块导入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())

Calling the init-function of a module prior to importing it (actually the module will not be really imported a second time, only looked up in the cache) works also if there are cyclic dependencies between modules.如果模块之间存在循环依赖关系,则在导入之前调用模块的 init 函数(实际上该模块不会真正第二次导入,只会在缓存中查找)也有效。 For example if module p2 imports module p3 , which imports p2 in its turn.例如,如果模块p2导入模块p3 ,则模块依次导入p2


Warning: Since Cython 0.29, Cython uses multi-phase initialization per default for Python>=3.5, thus calling PyInit_p2 is not enough (see eg this SO-post ).警告:从 Cython 0.29 开始,Cython 默认使用 Python>=3.5 的多阶段初始化,因此调用PyInit_p2是不够的(参见例如这个 SO-post )。 To switch off this multi-phase initialization -DCYTHON_PEP489_MULTI_PHASE_INIT=0 should be passed to gcc or similar to other compilers.要关闭这个多阶段初始化-DCYTHON_PEP489_MULTI_PHASE_INIT=0应该传递给 gcc 或类似的其他编译器。


Note: However, even after all of the above, the embedded interpreter will need its standard libraries (see for example this SO-post ) - there is much more work to do to make it truly standalone!注意:但是,即使完成了上述所有操作,嵌入式解释器仍将需要其标准库(例如,请参阅此SO-post )-要使其真正独立,还有很多工作要做! So maybe one should heed @DavidW's advice :所以也许应该听听@DavidW 的建议

"don't do this" is probably the best solution for the vast majority of people. “不要这样做”可能是绝大多数人的最佳解决方案。


A word of warning: if we declare PyInit_p2() as警告:如果我们将PyInit_p2()声明为

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

PyInit_p2(); # TODO: error handling if NULL is returned

Cython will no longer handle the errors and its our responsibility. Cython 将不再处理错误及其责任。 Instead of代替

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;

produced for object -version, the generated code becomes just:object -version 生成,生成的代码变为:

(void)(PyInit_p2());

ie no error checking!即没有错误检查!

On the other hand using另一方面使用

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

will not work with g++ - one has to add extern C to declaration.不适用于 g++ - 必须将extern C添加到声明中。

People are tempted to do this because it's fairly easy to do for the simplest case (one module, no dependencies).人们很想这样做,因为对于最简单的情况(一个模块,没有依赖项)来说,这很容易做到。 @ead's answer is good but honestly pretty fiddly and it is handling the next simplest case (two modules that you have complete control of, no dependencies). @ead 的回答很好,但老实说非常繁琐,它正在处理下一个最简单的情况(您可以完全控制的两个模块,没有依赖项)。

In general a Python program will depend on a range of external modules.通常,Python 程序将依赖于一系列外部模块。 Python comes with a large standard library which most programs use to an extent. Python 附带了一个大型标准库,大多数程序都在一定程度上使用了它。 There's a wide range of third party libraries for maths, GUIs, web frameworks.有大量用于数学、GUI、Web 框架的第三方库。 Even tracing those dependencies through the libraries and working out what you need to build is complicated, and tools such as PyInstaller attempt it but aren't 100% reliable.即使通过库跟踪这些依赖项并计算出您需要构建的内容也很复杂,而 PyInstaller 等工具也尝试过,但并非 100% 可靠。

When you're compiling all these Python modules you're likely to come across a few Cython incompatibilities/bugs.当您编译所有这些 Python 模块时,您可能会遇到一些 Cython 不兼容/错误。 It's generally pretty good, but struggles with features like introspection, so it's unlikely a large project will compile cleanly and entirely.它通常非常好,但在自省等功能方面遇到困难,因此大型项目不太可能干净且完整地编译。

On top of that many of those modules are compiled modules written either in C, or using tools such as SWIG, F2Py, Cython, boost-python, etc.. These compiled modules may have their own unique idiosyncrasies that make them difficult to link together into one large blob.最重要的是,这些模块中有许多是用 C 编写的编译模块,或者使用诸如 SWIG、F2Py、Cython、boost-python 等工具编写的模块。这些编译模块可能有自己独特的特性,使它们难以链接在一起成一大团。

In summary, it may be possible, but for non-trivial programs it is not a good idea however appealing it seems.总而言之,这是可能的,但对于非平凡的程序来说,无论它看起来多么吸引人,这都不是一个好主意 Tools like PyInstaller and Py2Exe that use a much simpler approach (bundle everything into a giant zip file) are much more suitable for this task (and even then they struggle to be really robust). PyInstaller 和 Py2Exe 之类的工具使用更简单的方法(将所有内容捆绑到一个巨大的 zip 文件中)更适合此任务(即使如此,它们也很难真正健壮)。


Note this answer is posted with the intention of making this question a canonical duplicate for this problem.请注意,发布此答案的目的是使此问题成为此问题的规范副本。 While an answer showing how it might be done is useful, "don't do this" is probably the best solution for the vast majority of people.虽然显示如何完成的答案很有用,但“不要这样做”可能是绝大多数人的最佳解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM