您所在的位置:读书频道 > 设计开发 > 其它开发 > 8.1.1 Python 源码中的PyFrameObject

8.1.1 Python 源码中的PyFrameObject

2008-07-23 15:46 陈儒著 电子工业出版社 字号:T | T
一键收藏,随时查看,分享好友!

《Python源码剖析--深度探索动态语言核心技术》第8章Python 虚拟机框架,从这一章开始,我们将切入Python 字节码虚拟机,深入剖析Python 字节码虚拟机的运行机理,本小节为大家介绍的是Python 源码中的PyFrameObject。

AD:

8.1.1 Python 源码中的PyFrameObject

当然,对于Python 而言,PyFrameObject 对象不仅仅是一个我们在x86 机器上看到的那个简简单单的栈帧,它实际上包含了其他更多的信息。请看Python 源码中对PyFrame-Object 的定义:

 [frameobject.h]
typedef struct _frame {
PyObject_VAR_HEAD
struct _frame *f_back; //执行环境链上的前一个frame
PyCodeObject *f_code; //PyCodeObject 对象
PyObject *f_builtins; //builtin 名字空间
PyObject *f_globals; //global 名字空间
PyObject *f_locals; //local 名字空间
PyObject **f_valuestack; //运行时栈的栈底位置
PyObject **f_stacktop; //运行时栈的栈顶位置
……
int f_lasti; //上一条字节码指令在f_code 中的偏移位置
int f_lineno; //当前字节码对应的源代码行
……
//动态内存,维护(局部变量+cell 对象集合+free
对象集合+运行时栈)所需要的空间
PyObject *f_localsplus[1];
} PyFrameObject;

从f_back 我们可以看出一点,在Python 实际的执行中,会产生很多PyFrameObject对象,而这些对象会被链接起来,形成一条执行环境链表。这正是对x86 机器上栈帧间关系的模拟。在x86 上,栈帧间通过esp 指针和ebp 指针建立了关系,使新的栈帧在结束之后能顺利回到旧的栈帧中,而Python 正是利用f_back 来完成这个动作。那真实的情况是不是这样呢,我们暂且按下不表。

在f_code 中存放的是一个待执行的PyCodeObject 对象,而接下来的f_builtins、f_globals、f_locals 是3 个独立的名字空间,在这里我们看到了名字空间和执行环境之间的关系。前面我们说名字空间实际上是维护着变量名和变量值之间关系的PyDictObject对象,所以,在这3 个PyDictObject 中,分别维护了builtin 的name、global 的name,以及local 的name 与对应值之间的映射关系。在下一节,我们将给出关于名字空间的详细解析,因为它对于理解Python 虚拟机的行为相当关键。想想前面的那段environment.py,在执行print i 时,首先会到f_locals 中去寻找PyStringObject 对象‘i’,找到了之后,将其对应的值取出,并打印出来。

在PyFrameObject 的开头,有一个PyObject_VAR_HEAD,这表明PyFrameObject 是一个变长的对象,即每次创建的PyFrameObject 对象的大小可能是不一样的。这些变动的内存是用来做什么的呢?实际上,每一个PyFrameObject 对象都维护了一个PyCode-Object 对象。这表明每一个PyFrameObject 对象和Python 源代码中的一段Code 都是对应的,更准确地说,是和我们在研究PyCodeObject 时提到的那个Code Block 对应的。而在编译一段Code Block 时,会计算出这段Code Block 执行过程中所需要的栈空间的大小(注意,这个栈空间才是和x86 机器上那个用于函数执行的栈空间相对应的概念)。这个栈空间的大小存储在f_stacksize 中,而栈本身正是那段变动的内存。因为不同的CodeBlock 在执行时所需的栈空间的大小是不同的,所以决定了PyFrameObject 的开头一定有一个PyObject_VAR_HEAD。

前面我们说PyFrameObject 对象是对x86 机器上单个栈帧的模拟。既然在x86 的单个栈帧中,包含了执行计算所必需的内存空间,为什么执行计算还需要内存空间呢?举个例子:在计算c=a+b 时,我们需要将a 和b 的值分别读入内存,然后计算的结果也需要存放在内存中,这些内存就是执行计算所必需的内存。当然,在x86 上,完成一条加法操作只需要CPU 中的寄存器即可,这里仅仅展示了在计算的过程中是需要消耗一定的内存的。所以作为对x86 栈帧的模拟,在PyFrameObject 中,也提供了对这些内存空间的模拟。在今后的描述中,我们将其称为运行时栈。注意,一定要将这里的“运行时栈”的概念和x86 平台上的“运行时栈”区分开来。我们这里所谓的“运行时栈”单指运算时所需要的内存空间。

与图8-1 所示的x86 平台上的运行时栈对应,图8-2 展示了Python 虚拟机在运行时某个时刻的完整运行时环境。

 
(点击查看大图)图8-2 Python 执行的某个时刻的运行时环境
虽然在图8-1 中连续的内存空间到图8-2 已经变成了分离的内存空间,但是对比图8-1和图8-2,我们仍然能够看出它们之间的相似之处。
【责任编辑:夏书 TEL:(010)68476606】

回书目   上一节   下一节

分享到:

Python Cookbook(第3版)中文版

本书介绍了Python应用在各个领域中的一些使用技巧和方法,其主题[详细]

  1. Linux服务器配置全程实录
  2. 揭秘--优秀PPT这样制作

热点职位

更多>>

热点专题

更多>>

读书

Visual Studio Team Systems软件工程实践
本书论述了软件开发价值增加的思维方式。这一思维方式构成了VSTS的基础,包括VSTS的指导思想,为什么这些指导思想会以某些方式表

51CTO旗下网站

领先的IT技术网站 51CTO 中国首个CIO网站 CIOage 中国首家数字医疗网站 HC3i 51CTO学院