Python之PyFrameObject动态执行环境
Python虚拟机中的执行环境
Python的虚拟机实际上是在模拟操作系统运行可执行文件的过程,首先,我们先来讲一下普通的x86的机器上,可执行文件是以一种什么方式运行的。

图1-1
图1-1所展示的运行时栈的情形可以看作是如下的C代码运行时情形:
#include <stdio.h>
void f(int a, int b)
{
printf("a=%d, b=%d\n", a, b);
}
void g()
{
f(1, 2);
}
main(int argc, char const *argv[])
{
g();
return 0;
}
esp:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶.
ebp:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部
当程序的流程进入函数f时,图1-1,其中“调用者的帧”是函数g的栈帧,而“当前帧”则是函数f的栈帧。对于一个函数而言,其所有局部变量的操作都在自己的栈帧中完成,而函数间的调用则通过创建新的栈帧完成
图1-1所示的系统中,运行时栈是从地址空间的高地址向低地址延伸的。当在函数g中执行函数f的调用时,系统就会在地址空间中,于g的栈帧之后,创建f的栈帧。当然,在发生函数调用时,系统会保存上一个栈帧的栈指针esp和帧指针ebp。当函数f执行完成之后,系统会把esp和ebp的值恢复为创建f的栈帧之前的值。这样,程序的流程又回到函数g中,而程序的工作空间则又回到函数g的栈帧中。这就是可执行文件再x86机器上的大致运行原理。而Python正是在虚拟机中通过不同的实现方式模拟了这一原理,从而完成了Python字节码指令序列的执行。
我们之前在Python之code对象与pyc文件(一)、Python之code对象与pyc文件(二)、Python之code对象与pyc文件(三)中剖析了,PyCodeObject对象包含了程序中的静态信息,然而有一点PyCodeObject对象没有包含,那就是关于程序运行时的动态信心——执行环境
什么是执行环境呢?考虑下面的一个例子:
env.py
i = "Python" def f():
i = 999
print(i) # <1> f()
print(i) # <2>
在代码<1>和<2>两个地方,都执行了同样的动作,打印变量i的值。显然,它们所对应的字节码指令肯定是相同的,但这两条语句的执行效果肯定不同。正是因为执行环境的影响,所以在<1>处会打印999,在<2>处会打印Python。像这种同样的符号在程序运行的不同时刻对应不同的值,甚至不同类型的情况,必须在运行时动态的捕捉和维护。这些信息是不可能在PyCodeObject对象中被静态存储的
这里简单介绍Python中的一个名词——名字空间:
- python中,每个函数都有一个自己的名字空间,通过locals()访问,它记录了函数的变量
- python中,每个module有一个自己的名字空间,通过globals()访问,它记录了module的变量,包括 functions, classes 和其它imported modules,还有 module级别的变量和常量
- 还有一个builtins名字空间,可以被任意模块访问,这个builtins名字空间存储着 class object、class Exception、def len等一些基础类型和基础函数
x = 3 def f1(x=1):
y = 2
print("locals:", locals()) f1()
print("globals:", globals())
运行结果:
locals: {'y': 2, 'x': 1}
globals: {'__name__': '__main__',…………, 'x': 3, 'f1': <function f1 at 0x000000DA28CE3E18>}
名字空间是执行环境的一部分,除了名字空间,在执行环境中,还包含了一些其他信息
结合x86平台运行可执行文件的机理,我们可以用这样的机理来解释env.py的执行过程。当Python开始执行env.py中第一条表达式时,Python已经建立起一个执行环境A,所有的字节码指令都会在这个执行环境中执行。Python可以从这个执行环境中获取变量的值,也可以根据字节码的指令修改执行环境中某个变量的值,以影响后续程序的运行。这样的过程会一直持续下去,直到发生了函数的调用行为
当Python在执行环境A中执行调用函数f的字节码指令时,会在当前的执行环境A之外重新创建一个新的执行环境B,在这个新的执行环境B中,有一个新的名字为"i"的对象。所以,新的执行环境B可以对应图1-1这种所示的新的栈帧
所以在Python真正执行的时候,它的虚拟机实际上面对的并不是一个PyCodeObject对象,而是另外一个对象——PyFrameObject,它就是我们所说的执行环境,也是Python对x86平台上栈帧的模拟
Python源码中的PyFrameObject
Python源码中PyFrameObject的定义:
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中的偏移位置 */
/* As of 2.3 f_lineno is only valid when tracing is active (i.e. when
f_trace is set) -- at other times use PyCode_Addr2Line instead. */
int f_lineno; /* 当前字节码对应的源代码行 */
int f_iblock; /* index in f_blockstack */
…………
//动态内存、维护(局部变量+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个独立的名字空间,如我们所说,名字空间是执行环境的一部分。当执行env.py时,当要打印i这个变量,会去f_locals中寻找i这个PyStringObject变量,找到后将其对应的值取出,再打印出来
在PyFrameObject开头,有一个PyObject_VAR_HEAD,这表明PyFrameObject是一个变长对象,即每次创建PyFrameObject对象的大小可能是不一样的,这些变动的内存是用来做什么呢?实际上,每一个PyFrameObject对象都维护着一个PyCodeObject对象。这表明每一个PyFrameObject对象和Python源码中的一段code都是对应的,更准确的说,是和我们研究PyCodeObject时提到的Code Block对应的。而在编译一段Code Block时,会计算出这段Code Block执行过程中所需要的栈空间大小。这个栈空间大小存储在PyCodeObject的co_stacksize中。因为不同的Code Block在执行时所需的栈空间大小不同,所以决定PyFrameObject的开头一定有一个PyObject_VAR_HEAD
PyFrameObject对象是对x86机器上单个栈帧活动的模拟,既然在x86的单个栈帧中,包含了计算所需的内存空间,为什么执行计算还需要内存空间呢?举个例子:在计算c=a+b时,我们需要将a和b的值读入内存,然后计算结果也要存放在内存中,这些内存就是执行计算所必须的内存,然后计算结果也要存放在内存中,这些内存就是执行计算所必须的内存。所以,作为对x86栈帧的模拟,在PyFrameObject中,也提供了这些对内存空间的模拟。这里,我们称为“运行时栈”。注意:这里的“运行时栈”的概念和x86平台上的“运行时栈”有所不同,我们这里所谓的“运行时栈”单指运算时所需的内存空间

图1-2
图1-2展示了Python虚拟机在某个运行时刻的完整运行环境
PyFrameObject中的动态内存空间
在PyFrameObject对象所维护的运行时栈中,存储的都是PyObject *,f_localsplus维护着一段变动长度的内存,但这段内存并不只是给栈使用,还有别的对象也会使用
PyFrameObject *
PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
PyObject *locals)
{
PyFrameObject *back = tstate->frame;
PyFrameObject *f;
PyObject *builtins;
Py_ssize_t i; if (code->co_zombieframe != NULL) {
f = code->co_zombieframe;
code->co_zombieframe = NULL;
_Py_NewReference((PyObject *)f);
assert(f->f_code == code);
}
else {
Py_ssize_t extras, ncells, nfrees;
ncells = PyTuple_GET_SIZE(code->co_cellvars);
nfrees = PyTuple_GET_SIZE(code->co_freevars);
//四分部构成PyFrameObject维护的动态内存区,其大小由extras决定
extras = code->co_stacksize + code->co_nlocals + ncells + nfrees;
//计算初始化时运行时栈的栈顶
extras = code->co_nlocals + ncells + nfrees;
f->f_valuestack = f->f_localsplus + extras;
}
f->f_stacktop = f->f_valuestack; return f;
}
从上面的代码可以知道,创建PyFrameObject对象时,额外申请的那部分内存中有一部分是给PyCodeObject对象中存储的那些局部变量:co_freevars、co_cellvars。而另一部分才是给运行时栈使用的。所以,PyFrameObject对象中栈的起始位置(也就是栈底)是又f_valuestack维护的,而f_stacktop维护额当前的栈顶

图1-3
图1-3是一个刚被创建的PyFrameObject对象的示意图,,从中可以看到运行时栈和PyFrameObject对象中动态内存部分的关系
在Python中访问PyFrameObject对象
在Python中,有一种frame object,它是对C一级的PyFrameObject的包装,而且Python还提供了一个方法能方便地获得当前处于活动状态的frame object。这个方法就是sys module中的_getframe方法
import sys value = 3 def g():
frame = sys._getframe()
print("current function is:", frame.f_code.co_name)
caller = frame.f_back
print("caller function is:", caller.f_code.co_name)
print("caller's local namespace:", caller.f_locals)
print("caller's global namespace:", caller.f_globals.keys()) def f():
a = 1
b = 2
g() def show():
f() show()
运行结果:
current function is: g
caller function is: f
caller's local namespace: {'b': 2, 'a': 1}
caller's global namespace: dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__annotations__', '__builtins__', '__file__', '__cached__', 'sys', 'value', 'g', 'f', 'show'])
从执行结果可以看到,在函数f中可以通过caller完全获得其调用者g函数的信息,甚是是g的各个名字空间
Python之PyFrameObject动态执行环境的更多相关文章
- Python基础:27执行环境
一:可调用对象 可调用对象,是任何能通过函数操作符“()”来调用的对象.Python 有4 种可调用对象:函数,方法,类,以及一些类的实例. 1:函数 python 有 3 种不同类型的函数对象. a ...
- 配置EditPlus编辑器使其成为Python的编辑、执行环境
1.添加Python群组 运行EditPlus,选择工具→配置用户工具进入参数设置框. 单击添加工具→应用程序.菜单文字输入python,命令为Python的安装路径,参数输入 $(FileName) ...
- Python入门笔记(26):Python执行环境
一.python特定的执行环境 在当前脚本继续进行 创建和管理子进程 执行外部命令或程序 执行需要输入的命令 通过网络来调用命令 执行命令来创建需要处理的输出 动态生成Python语句 导入Pytho ...
- Python学习(25):Python执行环境
转自 http://www.cnblogs.com/BeginMan/p/3191856.html 一.python特定的执行环境 在当前脚本继续进行 创建和管理子进程 执行外部命令或程序 执行需要输 ...
- python执行环境
转自 http://www.cnblogs.com/BeginMan/p/3191856.html 一.python特定的执行环境 在当前脚本继续进行 创建和管理子进程 执行外部命令或程序 执行需要输 ...
- 第6.6节 Python动态执行小结
一. Python动态执行支持通过输入数据流或文件传入Python源代码串,进行编译后执行,可以通过这种方式扩展Python程序的功能: 二. 动态执行方法可能导致恶意攻击,因此使用时需要 ...
- 第6.2节 Python特色的动态可执行方法简介
一. 基本概念 动态可执行,是指在代码中通过外部输入或代码嵌入的常量字符串包含代码的方式提供Python代码,要求Python执行这些代码.这样就可以达到开放式运行的效果,提高程序的能力和灵活性 ...
- Python核心编程读笔 13:执行环境
第14章 执行环境 一.可调用对象 python有四种可调用对象:函数.方法.类.一些类的实例 1 函数 (1)内建函数(BIF) BIF是用c/c++写的,编译后放入python解释器,然后把它们 ...
- django系列5.4--ORM中执行原生SQL语句, Python脚本中调用django环境
ORM执行原生sql语句 在模型查询API不够用的情况下,我们还可以使用原始的SQL语句进行查询. Django 提供两种方法使用原始SQL进行查询:一种是使用raw()方法,进行原始SQL查询并返回 ...
随机推荐
- asp.net MVC 4.0 View回顾——布局页与分部页
asp.net MVC 4.0中总结 视图里加载部分视图几种方法 @RenderPage() 但它不能使用 原来视图的 Model 和 ViewData ,只能通过参数来传递. @RenderPage ...
- 3.Freshman阶段学习内容的确定
我刷知乎.在知乎上答题的程序员,不是很牛逼就是更牛逼,说起各种系统.各种系统的各种版本.各种语言.数据库.算法.IT届的各种圣战都有板有眼.信手拈来.头头是道,不得不服.这导致了一些非常严重的问题:我 ...
- css3Transitions 实现的鼠标经过图标位移、旋转、翻转、发光、淡入淡出等多种特效
HTML如下: 1 <div class="container"> 3 <!--特效1 --> <section id="set-1&q ...
- If people in the communications only think about gains and losses of interest, then the pleasure of knowing each other will cease to exist.
If people in the communications only think about gains and losses of interest, then the pleasure of ...
- UIButton 图片文字位置
在实际开发过程中经常在按钮上添加文字和图片,位置和图片的位置根据需求放置也是不一样的.下面实现了各种显示方式,如下图: UIButton+LSAdditions.h // // UIButton+LS ...
- Python+selenium之跳过测试和预期失败
在运行测试时,需要直接跳过某些测试用例,或者当用例符合某个条件时跳过测试,又或者直接将测试用例设置为失败.unittest单元测试框架提供了实现这些需求的装饰器. 1.unittest.skip(re ...
- Arria II GX FPGA开法套件——初步使用
1. 从官网下载使用手册和参考手册,以及开发包 下载地址:https://www.altera.com.cn/products/boards_and_kits/dev-kits/a ...
- java面试题(杨晓峰)---第二讲Exception和Error有什么区别?
本人总结: Exception和Error:正常问题和意外问题,以自行车举例:没气和爆胎. ①理解Throwable,Exception,Error的设计和分类. ②掌握哪些应用最广泛的子类, ③如何 ...
- Intel 快速存储蓝屏
今天电脑蓝屏,DPC Watchdog Violation 很烦.开bluescreen说是NT内核的问题 开windbg说是Intel快速存储的问题,顺手卸载快速存储 卸载前 卸载后 另外我看Int ...
- (外挂破解)Cheat Engine(内存修改工具)V6.2中文版软件介绍
Heat Engine是一款内存修改编辑工具,Cheat Engine允许你修改你的游戏,所以你将总是赢.它包括16进制编辑,反汇编程序,内存查找工具.与同类修改工具相比,它具有强大的反汇编功能,且自 ...