Python Bytecode究竟在CPython中运行得如何?

我想了解Python如何工作(因为我一直都在使用它!)。 据我的理解,当你运行像python script.py这样的脚本时,脚本被转换为字节码,然后解释器/ VM / CPython--实际上就是一个C程序 - 读取python字节码并相应地执行程序。

这个字节码是如何读入的? 它与C中如何读取文本文件类似吗? 我不确定Python代码如何转换为机器代码。 Python解释器(CLI中的python命令)实际上只是一个预编译的C程序,它已经被转换为机器码,然后python字节码文件只能通过该程序? 换句话说,我的Python程序是否从未真正转换为机器代码? python解释器已经在机器代码中了,所以我的脚本永远不会是?


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

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

像Pypy这样的其他实现具有JIT编译功能,即它们可以将Python快速转换为机器码。


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

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

dis文档解释每个字节码的含义。 例如, LOAD_NAME

将与co_names[namei]关联的值推入堆栈。

为了理解这一点,你必须知道字节码解释器是一个虚拟堆栈机器,以及co_names是什么。 inspect模块文档有一个很好的表格,显示了最重要的内部对象的最重要属性,因此您可以看到co_namescode对象的一个​​属性,它包含一个局部变量名称的元组。 换句话说, LOAD_NAME 0推压与第0个局部变量相关联的值(和dis有益看起来这个向上并看到第0局部变量被命名为'i' )。

这足以看出一串字节码是不够的; 解释器还需要代码对象的其他属性,以及某些情况下函数对象的属性(也是本地和全局环境来自的地方)的属性。

inspect模块也有一些工具可以帮助您进一步调查实时代码。

这足以弄清楚很多有趣的东西。 例如,你可能知道Python在编译时计算出函数中的变量是局部变量,闭包变量还是全局变量,这取决于你是否在函数体中的任何地方(以及任何nonlocalglobal语句)赋值它。 如果你编写三个不同的函数并比较它们的反汇编(以及相关的其他属性),你可以很容易地弄清楚它必须做什么。

(这里有一点棘手的是理解闭包单元,为了真正解决这个问题,你需要有3个层次的功能,看看中间的一个如何为最内层的单元转发。)


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


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


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

CPython编译器的设计解释了一切如何工作。 (Python开发人员指南的其他部分也很有用。)

对于早期的东西 - 标记和解析 - 您可以使用ast模块来跳转到需要做实际编译的时间点。 然后请参阅compile.c了解AST如何变成字节码。

这些宏可能有点难以实现,但是一旦掌握了编译器如何使用堆栈下降到块的想法,以及它如何使用这些compiler_addop和好友在当前级别发出字节码,这一切都是有道理的。

起初让大多数人惊讶的一件事是功能的运作方式。 函数定义的主体被编译成一个代码对象。 然后,函数定义本身被编译成代码(在封闭函数体,模块等内部),该代码在执行时从该代码对象构建函数对象。 (一旦你想到闭包必须如何工作,为什么它会以这种方式工作,闭包的每个实例都是一个具有相同代码对象的独立函数对象。)


现在你已经准备好开始修补CPython来添加你自己的语句了,对吧? 那么,正如改变CPython的语法所显示的那样,有很多东西可以正确使用(如果你需要创建新的操作码,还有更多的东西)。 您可能会发现学习PyPy和CPython会更容易,并且首先开始对PyPy进行黑客攻击,一旦知道您正在做的事情是明智和可行的,则只会回到CPython。


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

本文是由Yaniv Aknin编写的系列文章的一部分,我是一位粉丝:Python的Innards

链接地址: http://www.djcxy.com/p/86483.html

上一篇: How exactly is Python Bytecode Run in CPython?

下一篇: threads and thread in Python