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_names
是code
对象的一个属性,它包含一个局部variables名称的元组。 换句话说, LOAD_NAME 0
推送与第0个局部variables相关的值(并有助于查看,并看到第0个局部variables被命名为'i'
)。
这足以看出一串字节码是不够的; 解释器还需要代码对象的其他属性,在某些情况下还需要函数对象的属性(这也是本地和全局环境的来源)。
inspect
模块还有一些工具可以帮助您进一步调查实时代码。
这足以弄清楚很多有趣的东西。 例如,你可能知道Python在编译时计算出函数中的variables是局部variables,闭包variables还是全局variables,这取决于你是否在函数体中的任何位置(以及任何nonlocal
或global
语句)赋值。 如果你写了三个不同的函数,并比较它们的反汇编(以及相关的其他属性),你可以很容易地弄清楚它必须做什么。
(这里有一点棘手的是理解闭包单元,为了真正理解闭包单元,你需要有3个层次的function,看看中间的一个是如何为最内层的单元转发的。)
为了理解字节码是如何解释的以及堆栈机器是如何工作的(在CPython中),你需要看看ceval.c
源代码。 thy435和eyquem的答案已经涵盖了这个。
了解如何pyc
文件只读取需要更多的信息。 内德·巴切尔德(Ned Batchelder)有一个很棒的(如果稍微过时的)博客文章,名为The .pyc文件的结构 ,涵盖了所有棘手的,没有很好logging的部分。 (请注意,在3.3中,一些与导入有关的血腥代码已经从C移到了Python,这使得它更容易遵循。)但基本上,它只是一些头信息和模块的code
对象,由marshal
序列化。
为了理解源代码如何编译成字节码,这是非常有趣的部分。
CPython编译器的devise解释了一切如何工作。 ( Python开发人员指南的其他部分也是有用的。)
对于早期的东西,标记和parsing,你可以使用ast
模块来跳转到需要做实际编译的时候。 然后请参阅compile.c
了解AST如何变成字节码。
这些macros可能有点难以实现,但是一旦掌握了编译器如何使用堆栈下降到块的想法,以及如何使用compiler_addop
和好友在当前级别发出字节码,这一切都是有道理的。
起初让大多数人惊讶的一件事是function的运作方式。 函数定义的主体被编译成一个代码对象。 然后函数定义本身被编译成代码(在封闭的函数体,模块等内部),当执行时,从该代码对象构build一个函数对象。 (一旦你考虑了闭包必须如何工作,为什么它是这样工作的,闭包的每个实例都是一个单独的具有相同代码对象的函数对象。)
现在您已经准备好开始修补CPython来添加自己的语句了,对吗? 那么,正如改变CPython的语法所显示的那样,有很多东西可以正确的使用(如果你需要创build新的操作码,还有更多的东西)。 你可能会发现,学习PyPy和CPython会更容易,并且首先开始对PyPy进行黑客攻击,一旦你知道你正在做的事情是明智而可行的,就只能回到CPython。
在阅读了thg4535的答案之后,我相信你会发现ceval.c的下列解释很有意思: 你好,ceval.c!
这篇文章是Yaniv Aknin写的一个系列文章的一部分,我是一个粉丝: Python的Innards