Python解释器简介(5):深入主循环
admin
2023-07-30 22:05:20
0
  • Python解释器简介(1):函数对象
  • Python解释器简介(2):代码对象
  • Python解释器简介(3):理解字节码
  • Python解释器简介(4):动态语言

本文将会带领大家了解 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):函数对象

  • Python解释器简介(2):代码对象
  • Python解释器简介(3):理解字节码
  • Python解释器简介(4):动态语言
  • 本文将会带领大家了解 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;     

    相关内容

    热门资讯

    500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
    定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
    65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
    Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
    scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
    pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
    小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
    微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
    Prometheus+Graf... 一,Prometheus概述 1,什么是Prometheus?Prometheus是最初在Sound...
    python绘图库Matplo... 本文简单介绍了Python绘图库Matplotlib的安装,简介如下: matplotlib是pyt...