Python字节码在CPython中究竟是如何运行的?

2024-04-25 23:08:16 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图理解Python是如何工作的(因为我一直在使用它!)。据我所知,当您运行类似python script.py的脚本时,该脚本将转换为字节码,然后解释器/VM/CPython(实际上只是一个C程序)读取python字节码并相应地执行该程序。

这个字节码是如何读入的?它类似于C语言中文本文件的读取方式吗?我不确定Python代码是如何转换成机器代码的。Python解释器(CLI中的Python命令)真的只是一个预编译的C程序,它已经被转换成机器代码,然后Python字节码文件就被放到这个程序中了吗?换句话说,我的Python程序从来没有真正转换成机器代码吗?python解释器已经在机器代码中了吗,所以我的脚本不必是?


Tags: 文件代码py命令程序脚本机器cli
3条回答

如果您想查看某些代码(无论是源代码、活动函数对象还是代码对象等)的字节码,^{}模块将准确地告诉您需要什么。例如:

>>> dis.dis('i/3')
  1           0 LOAD_NAME                0 (i)
              3 LOAD_CONST               0 (3)
              6 BINARY_TRUE_DIVIDE
              7 RETURN_VALUE

文档解释了每个字节码的含义。例如,^{}

Pushes the value associated with co_names[namei] onto the stack.

要理解这一点,您必须知道字节码解释器是一个虚拟的stack machine,以及co_names是什么。^{}模块文档有一个很好的表,显示了最重要的内部对象的最重要属性,因此您可以看到co_namescode对象的一个属性,它包含一个局部变量名的元组。换句话说,LOAD_NAME 0推送与第0个局部变量相关联的值(并且dis有助于查找该值,并看到第0个局部变量名为'i')。

这足以说明字节码串是不够的;解释器还需要代码对象的其他属性,在某些情况下还需要函数对象的属性(这也是局部和全局环境的来源)。

inspect模块还提供了一些工具,可以帮助您进一步研究实时代码。

这足以找出很多有趣的东西。例如,您可能知道Python在编译时会根据您是否在函数体中的任何位置(以及任何nonlocalglobal语句)为函数中的变量赋值,计算出函数中的变量是局部变量、闭包变量还是全局变量;如果您编写三个不同的函数并比较它们的反汇编(以及相关的其他属性),您可以非常容易地找出它必须执行的操作。

(这里最棘手的一点是理解闭包单元。要真正做到这一点,您需要有3个级别的函数,以查看中间的那个函数是如何为最里面的那个函数传递信息的。)


要理解字节码是如何解释的以及堆栈机是如何工作的(在CPython中),您需要查看^{}源代码。由thy435和eyquem提供的答案已经涵盖了这一点。


了解如何读取pyc文件需要更多信息。Ned Batchelder有一篇很棒的博客文章,名为The structure of .pyc files(如果稍微过时的话),它涵盖了所有棘手的和没有很好文档记录的部分。(注意,在3.3中,与导入相关的一些血腥代码已经从C移到了Python,这使得它更容易理解。)但基本上,它只是一些头信息和模块的code对象,由^{}序列化。


为了理解源代码是如何编译成字节码的,这是有趣的部分。

Design of CPython's Compiler解释了所有的工作原理。(有些Python Developer's Guide的其他部分也很有用。)

对于早期的标记化和解析,您可以使用^{}模块直接跳到实际编译的时间点。然后请参阅^{}了解如何将AST转换为字节码。

宏执行起来可能有点困难,但是一旦你了解了编译器如何使用堆栈来下放到块中,以及它如何使用那些compiler_addop和朋友在当前级别上发出字节码,这一切都是有意义的。

一开始让大多数人惊讶的是功能的工作方式。函数定义的主体被编译为代码对象。然后,函数定义本身被编译成代码(在封闭的函数体、模块等内部),在执行时,该代码对象将生成函数对象。(一旦考虑到闭包必须如何工作,很明显为什么会这样工作。闭包的每个实例都是具有相同代码对象的单独函数对象。)


现在你可以开始修补CPython来添加你自己的语句了,对吧?好吧,正如Changing CPython's Grammar所示,有很多事情要做(如果需要创建新的操作码,还有更多事情要做)。你可能会发现学习PyPy和CPython更容易,然后开始hacki先说PyPy,等你知道你所做的是明智的和可行的,再回到Cpyhon。

在阅读了thg4535的答案之后,我相信您会发现以下关于ceval.c的解释很有趣:Hello, ceval.c!

这篇文章是Yaniv Aknin写的系列文章的一部分,我有点像他的粉丝:Python's Innards

是的,你的理解是正确的。在CPython解释器中基本上(非常基本)有一个巨大的switch语句,它说“如果当前的操作码是某某,那么就这样那样做”。

http://hg.python.org/cpython/file/3.3/Python/ceval.c#l790

其他的实现,比如Pypy,有JIT编译,也就是说,它们将Python动态地转换成机器代码。

相关问题 更多 >