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

616 查看

本文将会带领大家了解 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