Python解释器简介(5):深入主循环

本文将会带领大家了解 CPython 3.3 中的 Python 解释器。我们首先一起来看 Python 解释器的一个简短的高层概述,然后对解释器实现过程中的一些有意思的代码块进行深入的探讨。我已经把这里探讨的函数名和文件名囊括进来了,你可以在源码中找到它们自行阅读深究。

概述

我们从 Python 虚拟机(又叫 Python 解释器)的一个高层概述开始。

Python 虚拟机有一个栈帧的调用栈。一个栈帧包含了给出代码块的信息和上下文,其中包括最后执行的字节码指令、全局和局部的命名空间、异常状态和调用栈帧的引用。每个栈帧有两个与其相关联的栈:block 栈和数据栈, 其中 block 栈在一些控制流(比如异常处理)中使用。Python 虚拟机的主要工作就是操作这三个类型的栈。

具体一些,我们假设有下面这样一段代码,解释器执行到被标记的行。下面便是当前情况下调用栈、block 栈以及数据栈的情况。

main.py

在这一时刻,解释器在嵌套函数的中间位置调用 bar 函数。此时在调用栈中有三个栈帧:模块层级的栈帧、foo 函数的栈帧以及 bar 函数的栈帧。当 bar 函数完成动作返回,调用栈中与 bar 函数关联的栈帧将会弹栈。通常每一个模块都会有一个与其对应的拥有新作用域的栈帧,函数调用和类定义也是如此。注意,每一次函数调用都会创建一个栈帧,在递归函数中,每一层的递归调用都会拥有自己的一个栈帧。

每一个栈帧都有自己的数据栈和 block 栈。独立的数据栈和 block 栈使解释器可以中断或恢复栈帧,这与生成器相似。

这里的情况示意很清楚了,我们深入到代码内部看一下。

堆栈结构对象 frameobj.c 创建一个 ceval.c 文件中定义的 PyEval_EvalCodeEx 栈帧。 这个栈帧在 ceval.c 文件中执行 PyEval_EvalFrameEx 栈帧。

栈帧都从哪儿来?

ceval.c 文件中的 PyEval_EvalCodeEx 函数创建了新的栈帧。我们在下面摘录了执行 code 对象的 PyEval_EvalCodeEx 函数。这个函数首先创建了一个新的栈帧,之后解析命令行参数(如果有的话)。倘若 code 对象是生成器,那么函数返回新的生成器;否则,栈帧将会运行直到返回,而返回值将被传递到上层。

ceval.c

大部分情况下是 C 函数 function_call 在调用 PyEval_EvalCodeEx。所有 Python 对象被调用的时候都会调用 function_call。每当一个可调用的 Python 对象被调用,都会写入一个 code 对象用来创建栈帧。

funcobject.c

上面说大部分情况下是 function_call 在调用 PyEval_EvalCodeEx。而 PyEval_EvalCodeEx 也可以被 entry point 调用,比如 pythonrun.c 中的 run_pyc_file 以及 import.c 中的 exec_code_in_module。这些函数很相似,区别只是在于它们取得 code 对象的方式(通过编译还是通过读文件)和运行的环境(比如命名空间不同)。

我们回到 PyEval_EvalFrameEx。 这个函数大约有2400行代码,占了 ceval.c 文件的大部分;其中1500行代码是一个庞大的 switch 声明。我的那篇《 1,500 line switch statement powering your Python》提到的就是它。PyEval_EvalFrameEx 占用了一个单独的栈帧,并且会运行直到它返回。

在本系列文章的第 3 篇我们介绍了字节码。对解释器来讲,字节码是一序列字节指令。我们回到本篇开头的例子,下面列出了这段代码和函数中 code 对象的详细拆解。

PyEval_EvalFrameEx 从字节码中的第一个字节开始执行,在本例中,从 foo 函数字节码中的 LOAD_CONST 字节开始,并且去找那个庞大 switch 中对应的 case。当执行完 switch 中找到的操作指令,栈帧将移动到下一个相关操作码继续整个程序。在一些地方,操作码会中止循环通过 goto 跳出switch,通过下面 RETURN_VALUE 示例。

为使你更好地理解它如何工作,我在下面列出了 PyEval_EvalFrameEx 函数中的一段摘录。不过像往常一样,我更希望你去阅读 CPython 的完整代码。

ceval.c

在第三篇中, 我们看到 BINARY_ADD 没有参数,从上面的 dis 的第四行输出来看并没有命令行参数。这有点奇怪,我们原本希望看到一个带有两个参数的二进制函数。现在通过看解释器的情况,我们便知道发生了什么: 这两个参数在栈帧的数据栈栈顶。 下面我给出了 bar 函数执行时候数据栈的情况。

打赏支持我翻译更多好文章,谢谢!

打赏译者

打赏支持我翻译更多好文章,谢谢!

任选一种支付方式

1 7 收藏 1 评论

关于作者:v7

微博:@_v7__ 个人主页 · 我的文章 · 17 ·   

相关文章

可能感兴趣的话题



直接登录
最新评论
跳到底部
返回顶部