本文将会带领大家了解 CPython 3.3 中的 Python 解释器。我们首先一起来看 Python 解释器的一个简短的高层概述,然后对解释器实现过程中的一些有意思的代码块进行深入的探讨。我已经把这里探讨的函数名和文件名囊括进来了,你可以在源码中找到它们自行阅读深究。
概述
我们从 Python 虚拟机(又叫 Python 解释器)的一个高层概述开始。
Python 虚拟机有一个栈帧的调用栈。一个栈帧包含了给出代码块的信息和上下文,其中包括最后执行的字节码指令、全局和局部的命名空间、异常状态和调用栈帧的引用。每个栈帧有两个与其相关联的栈:block 栈和数据栈, 其中 block 栈在一些控制流(比如异常处理)中使用。Python 虚拟机的主要工作就是操作这三个类型的栈。
具体一些,我们假设有下面这样一段代码,解释器执行到被标记的行。下面便是当前情况下调用栈、block 栈以及数据栈的情况。
main.py
1 2 3 4 5 6 7 8 9 |
def foo(): x = 1 def bar(y): z = y + 2 # <--- (3) ... and the interpreter is here. return z return bar(x) # <--- (2) ... which is returning a call to bar ... foo() # <--- (1) We're in the middle of a call to foo ... main.py |
1 2 3 4 5 6 7 8 9 10 |
c --------------------------- a | bar Frame | -> block stack: [] l | (newest) | -> data stack: [1, 2] l --------------------------- | foo Frame | -> block stack: [] s | | -> data stack: [<Function foo.<locals>.bar at 0x10d389680>, 1] t --------------------------- a | main (module) Frame | -> block stack: [] c | (oldest) | -> data stack: [<Function foo at 0x10d3540e0>] k --------------------------- |
在这一时刻,解释器在嵌套函数的中间位置调用 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
PyObject * PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals, PyObject **args, int argcount, PyObject **kws, int kwcount, PyObject **defs, int defcount, PyObject *kwdefs, PyObject *closure) { PyCodeObject* co = (PyCodeObject*)_co; PyFrameObject *f; PyObject *retval = NULL; PyObject **fastlocals, **freevars; PyThreadState *tstate = PyThreadState_GET(); /* [snip error-checking] */ f = PyFrame_New(tstate, co, globals, locals); /* <--------- new frame */ if (f == NULL) return NULL; fastlocals = f->f_localsplus; freevarsPython解释器简介(1):函数对象
本文将会带领大家了解 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
|