如何编译python文件可执行文件和不同的体系结构

时间:2019-05-05 20:06:39

标签: python compilation

我有一个简单的hello world脚本,如果我想将其编译为在Linux机器上执行的二进制文件,该如何使用? 即使使用不同的体系结构,也必须能够在不同的计算机上执行相同的文件

这必须编译python3.6文件

我尝试过这些:

Pyinstaller. 2 problems, too big when compiled and won't run on different archs to the compiled one
Nuitka. Size is perfectly fine but it won't run on machines with a different arch type to the original one that compiled it
Cython. Same problem as Nuitka

还有其他建议吗?

结果应为:

python3 hello.py
Hello World

--compiled--

./hello
Hello World

1 个答案:

答案 0 :(得分:1)

我今天开始扩展此答案,以提供示例代码。在这样做的过程中,我偶然发现了a recent blog post by Gregory SzorcPyOxidizer工具,从表面上看,它可以完成您在OP中寻找的更多任务。它使用Rust将Python简化为机器代码,尽管它仍然在技术上需要运行时,但它将运行时嵌入可执行文件中,因此您无需捆绑文件。如果您尝试一下,我会对您的想法感兴趣。

由于我仍然认为我以前的答案可能有用,所以这里是更新:

一般回答

我不知道100%的覆盖率解决方案。我认为这很大程度上取决于您的代码,复杂性,依赖关系和目标。

您提到尝试Cython。我认为除了您所看到的之外,还有更多东西。我对Cython不熟悉;当我阅读有关该主题的信息时,这只是一个选择。但是我认为您可以根据运行时要求使用它来构建本机可执行文件。

有关其他选项,请参见Is it feasible to compile Python to machine code?

您还可以编写一个小型C程序来实例化python解释器,并从单独的文件或已编译的字节串中加载已编译的python字节码,从而自己构建可执行文件。有关详细信息,请参见Python embedding/extending documentation

运行时问题

将Python代码构建到本机可执行文件中很容易(这称为 embeddding ),但是您说对了,您仍然需要Python运行时。但这是一个复杂性问题。对于足够小的用例,例如您的简单问候世界,您可以将实际依赖的元素捆绑到代码中。 (例如,静态链接到python库。)

如果您正在寻找一个可任意扩展的解决方案,那么一个常见的解决方案(从商业游戏到备份软件,我已经看到了这一解决方案)是将为目标平台预先编译的足够的python二进制运行时捆绑在一起,您可以预测或控制的安装前缀(即不是/usr)。该捆绑软件通常包括任何机器代码(共享库)和根据需要编译的python字节码(.pyc.pyo),而没有任何.py文件。这不一定包括python安装的全部内容-仅仅是应用程序所需的机器代码和字节码。

嵌入示例

这是一个非常简单的hello世界,用Python编写,但嵌入到C骨架中:

#include <Python.h>

int
main(int argc, char *argv[])
{
    wchar_t *name = Py_DecodeLocale(argv[0], NULL);
    if (name == NULL) {
        fprintf(stderr, "error: cannot decode program name\n");
        exit(1);
    }

    /* Setup */
    Py_SetProgramName(name);
    Py_Initialize();

    /* Load and run code */
    if (argc > 1)
        PyRun_SimpleString(argv[1]);
    else
        PyRun_SimpleString("print('Hello, world')");

    /* Importing and running code from a file is also possible.
     * See https://docs.python.org/3/c-api/veryhigh.html
     * and https://docs.python.org/3/c-api/import.html
     */

    /* Clean up */
    Py_Finalize();
    PyMem_RawFree(name);

    /* Ideally we should get a result from the interpreter
     * and evaluate it, returning conditionally. Py_FinalizeEx()
     * is a good way but it's only in Python 3.6+, so I'm leaving
     * it out of this example. */
    return 0;
}

为此,makefile可能如下:

PY=python3
PC=$(PY)-config

all: py

py: py.o
        $(CC) $$($(PC) --ldflags) -o $@ $< $$($(PC) --libs)

py.o: py.c
        $(CC) $$($(PC) --cflags) -c $<

构建并运行:

$ make
gcc $(python3-config --cflags) -c py.c
gcc $(python3-config --ldflags) -o py py.o $(python3-config --libs)
$ ./py
Hello, world
$ ./py 'print("hi")'
hi

您可以在strace下运行此命令,以查看运行时要求是什么:

$ strace -e trace=open ./py 2>&1 | grep py

...,然后您知道需要捆绑哪些文件。如果要拾取cpio文件中的每个文件,则将有一个可移植的应用程序。 (它只需要安装在同一位置即可。解决方法包括如上所述使用--prefix从源代码构建python,并使用ldconfig和PYTHON_PATH玩奇怪的游戏。)