将Python编译为机器代码是否可行?

时间:2008-09-26 09:51:52

标签: python c linker compilation

将Python(可能通过中间C表示)编译成机器代码是否可行?

据推测,它需要链接到Python运行时库,Python标准库中任何Python本身的部分都需要编译(并链接)。

另外,如果你想对表达式进行动态评估,你需要捆绑Python解释器,但也许不允许这样做的Python子集仍然有用。

它会提供任何速度和/或内存使用优势吗?据推测,Python解释器的启动时间将被消除(尽管共享库仍需要在启动时加载)。

13 个答案:

答案 0 :(得分:49)

正如@Greg Hewgill所说,有充分的理由说明为什么这并不总是可行的。但是,某些类型的代码(如非常算法代码)可以转换为“真正的”机器代码。

有几种选择:

  • 使用Psyco,动态发出机器代码。但是,您应该仔细选择要转换的方法/功能。
  • 使用Cython,这是一种Python- 之类的语言,编译成Python C扩展
  • 使用PyPy,其中包含来自RPy​​thon的转换程序(Python的受限子集,不支持Python的某些“动态”功能)到C或LLVM。
    • PyPy仍然是高度实验性的
    • 并非所有扩展都会出现

之后,您可以使用现有的一个软件包(freeze,Py2exe,PyInstaller)将所有内容放入一个二进制文件中。

总而言之:您的问题没有一般答案。如果您的Python代码对性能至关重要,请尝试使用尽可能多的内置功能(或者询问“如何使我的Python代码更快”问题)。如果这没有帮助,请尝试识别代码并将其移植到C(或Cython)并使用扩展名。

答案 1 :(得分:24)

尝试ShedSkin Python-to-C ++编译器,但它远非完美。还有Psyco - Python JIT,如果只需要加速。但恕我直言,这不值得努力。对于速度至关重要的代码部分,最佳解决方案是将它们编写为C / C ++扩展。

答案 2 :(得分:17)

py2c(http://code.google.com/p/py2c)可以将python代码转换为c / c ++ 我是py2c的独立开发者。

答案 3 :(得分:14)

PyPy是一个用Python重新实现Python的项目,使用编译作为本机代码作为实现策略之一(其他是使用JIT的VM,使用JVM等)。他们编译的C版本平均比CPython慢​​,但对某些程序要快得多。

Shedskin是一个实验性的Python-to-C ++编译器。

Pyrex是专为编写Python扩展模块而设计的语言。它旨在弥合优秀的,高级的,易于使用的Python世界与凌乱的低级C世界之间的差距。

答案 4 :(得分:14)

Nuitka是一个Python到C ++编译器,它链接到libpython。这似乎是一个相对较新的项目。作者在pystone基准测试中声称speed improvement超过了CPython。

答案 5 :(得分:10)

Pyrex是编译为C语言的Python语言的子集,由最初为Python构建list comprehensions的人完成。它主要是为构建包装器而开发的,但可以在更一般的环境中使用。 Cython是一个更积极维护的派雷克斯分支。

答案 6 :(得分:9)

乍一看这似乎是合理的,但是Python中有很多普通的东西不能直接映射到C表示而不需要承载很多Python运行时支持。例如,想到鸭子打字。 Python中用于读取输入的许多函数可以采用文件或类似文件的对象,只要它支持某些操作,例如。 read()或readline()。如果您考虑将此类支持映射到C所需的内容,您就会开始想象Python运行时系统已经完成的各种事情。

py2exe等实用程序将Python程序和运行时捆绑到一个可执行文件中(尽可能)。

答案 7 :(得分:5)

一些额外的参考:

答案 8 :(得分:3)

Jython有一个针对JVM字节码的编译器。字节码是完全动态的,就像Python语言本身一样!很酷。 (是的,正如Greg Hewgill的回答所说,字节码确实使用了Jython运行时,因此Jython jar文件必须随应用程序一起分发。)

答案 9 :(得分:2)

Psyco是一种即时(JIT)编译器:Python的动态编译器,运行代码的速度提高了2-100倍,但需要大量内存。

简而言之:它可以更快地运行您现有的Python软件,但源代码没有变化,但它不像C编译器那样编译成目标代码。

答案 10 :(得分:2)

答案是“是的,有可能”。您可以使用Python代码并尝试使用CPython API将其编译为等效的C代码。事实上,曾经有一个Python2C项目就是这样做的,但我多年没有听说过它(回到Python 1.5天就是我上次看到它的时候。)

您可以尝试尽可能地将Python代码转换为本机C,并在需要实际Python功能时回退到CPython API。在过去的一两个月里,我一直在玩弄这个想法。然而,这是一项非常多的工作,并且大量的Python特性很难转换为C:嵌套函数,生成器,除了简单类的简单类,任何涉及从模块外部修改模块全局变量的东西,等等等等。

答案 11 :(得分:2)

这不会将Python编译为机器代码。但允许创建一个共享库来调用Python代码。

如果您正在寻找的是从C运行Python代码而不依赖于execp的简单方法。您可以从python代码生成一个共享库,其中包含对Python embedding API的一些调用。那么应用程序就是一个共享库,你可以在许多其他库/应用程序中使用它。

这是一个创建共享库的简单示例,您可以使用C程序进行链接。共享库执行Python代码。

将要执行的python文件是pythoncalledfromc.py

# -*- encoding:utf-8 -*-
# this file must be named "pythoncalledfrom.py"

def main(string):  # args must a string
    print "python is called from c"
    print "string sent by «c» code is:"
    print string
    print "end of «c» code input"
    return 0xc0c4  # return something

您可以使用python2 -c "import pythoncalledfromc; pythoncalledfromc.main('HELLO')进行尝试。它将输出:

python is called from c
string sent by «c» code is:
HELLO
end of «c» code input

共享库将由以下callpython.h

定义
#ifndef CALL_PYTHON
#define CALL_PYTHON

void callpython_init(void);
int callpython(char ** arguments);
void callpython_finalize(void);

#endif

关联的callpython.c是:

// gcc `python2.7-config --ldflags` `python2.7-config --cflags` callpython.c -lpython2.7 -shared -fPIC -o callpython.so

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <python2.7/Python.h>

#include "callpython.h"

#define PYTHON_EXEC_STRING_LENGTH 52
#define PYTHON_EXEC_STRING "import pythoncalledfromc; pythoncalledfromc.main(\"%s\")"


void callpython_init(void) {
     Py_Initialize();
}

int callpython(char ** arguments) {
  int arguments_string_size = (int) strlen(*arguments);
  char * python_script_to_execute = malloc(arguments_string_size + PYTHON_EXEC_STRING_LENGTH);
  PyObject *__main__, *locals;
  PyObject * result = NULL;

  if (python_script_to_execute == NULL)
    return -1;

  __main__ = PyImport_AddModule("__main__");
  if (__main__ == NULL)
    return -1;

  locals = PyModule_GetDict(__main__);

  sprintf(python_script_to_execute, PYTHON_EXEC_STRING, *arguments);
  result = PyRun_String(python_script_to_execute, Py_file_input, locals, locals);
  if(result == NULL)
    return -1;
  return 0;
}

void callpython_finalize(void) {
  Py_Finalize();
}

您可以使用以下命令编译它:

gcc `python2.7-config --ldflags` `python2.7-config --cflags` callpython.c -lpython2.7 -shared -fPIC -o callpython.so

创建一个名为callpythonfromc.c的文件,其中包含以下内容:

#include "callpython.h"

int main(void) {
  char * example = "HELLO";
  callpython_init();
  callpython(&example);
  callpython_finalize();
  return 0;
}

编译并运行:

gcc callpythonfromc.c callpython.so -o callpythonfromc
PYTHONPATH=`pwd` LD_LIBRARY_PATH=`pwd` ./callpythonfromc

这是一个非常基本的例子。它可以工作,但根据库,可能仍然很难将C数据结构序列化为Python,从Python序列化为C.事情可以自动化......

Nuitka可能会有所帮助。

此外还有numba,但他们都不打算完全按照您的意愿行事。可以从Python代码生成C头,但前提是您指定如何将Python类型转换为C类型或者可以推断出该信息。有关Python ast分析器,请参阅python astroid

答案 12 :(得分:1)