本文将会带领大家了解 CPython 3.3 中的 Python 解释器。我们首先一起来看 Python 解释器的一个简短的高层概述,然后对解释器实现过程中的一些有意思的代码块进行深入的探讨。我已经把这里探讨的函数名和文件名囊括进来了,你可以在源码中找到它们自行阅读深究。
概述
我们从 Python 虚拟机(又叫 Python 解释器)的一个高层概述开始。
Python 虚拟机有一个栈帧的调用栈。一个栈帧包含了给出代码块的信息和上下文,其中包括最后执行的字节码指令、全局和局部的命名空间、异常状态和调用栈帧的引用。每个栈帧有两个与其相关联的栈:block 栈和数据栈, 其中 block 栈在一些控制流(比如异常处理)中使用。Python 虚拟机的主要工作就是操作这三个类型的栈。
具体一些,我们假设有下面这样一段代码,解释器执行到被标记的行。下面便是当前情况下调用栈、block 栈以及数据栈的情况。
main.py
| 123456789 | 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 |
| 12345678910 | 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
| 123456789101112131415161718192021222324252627282930313233343536 | 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
|