Python虚拟机框架(一)

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的各个名字空间

原文地址:https://www.cnblogs.com/beiluowuzheng/p/9383603.html

时间: 2024-11-01 16:40:30

Python虚拟机框架(一)的相关文章

《python源码剖析》笔记 Python虚拟机框架

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1. Python虚拟机会从编译得到的PyCodeObject对象中依次读入每一条字节码指令, 并在当前的上下文环境中执行这条字节码指令. Python虚拟机实际上是在模拟操作中执行文件的过程 PyCodeObject对象中包含了字节码指令以及程序的所有静态信息,但没有包含 程序运行时的动态信息--执行环境(PyFrameObject) 2.Python源码中的PyFrameObject

《python解释器源码剖析》第12章--python虚拟机中的函数机制

12.0 序 函数是任何一门编程语言都具备的基本元素,它可以将多个动作组合起来,一个函数代表了一系列的动作.当然在调用函数时,会干什么来着.对,要在运行时栈中创建栈帧,用于函数的执行. 在python中,PyFrameObject对象就是一个对栈帧的模拟,所以我们即将也会看到,python虚拟机在执行函数调用时会动态地创建新的PyFrameObject对象.随着函数调用链的增长,这些PyFrameObject对象之间也会形成一条PyFrameObject对象链,这条链就是对象x86平台上运行时栈

Learning Scrapy:《精通Python爬虫框架Scrapy》Windows环境搭建

之前用爬虫抓点数据的时候基本上就是urllib3+BeautifulSoup4,后来又加入requests,大部分情况就够用了.但是最近心血来潮想学一下Scrapy,于是找了本书——<精通Python爬虫框架Scrapy>.内容算是比较可以的,但是按书中附录搭建环境着实折腾了一点时间,于是想把碰到的问题总结一下,让大家也少走点弯路. 进入正题之前,有几点要说明一下: 安装这个环境有什么用?这个环境就是一个服务器,有需要你抓的网站,不会因现实中的网站改版而造成代码失效,书中测试代码的网站基本上都

python虚拟机

翻译自<Python Virtual Machine> Python 虚拟机 每个函数对象都和以下的三个结构: 1.包含参数的局部变量名称(in .__code__.varnames) 2.全局变量名称(in .__code__.co_names) 3.常数(in .__code__.co_consts) 在python定义函数的时候创建这些结构,它们被定义在函数对应的__code__对象. 如果我们定义如下: Def minimum(alist): m=None if let(alist)

教你分分钟学会用python爬虫框架Scrapy爬取心目中的女神

欢迎加入Python学习交流群:535993938  禁止闲聊 ! 名额有限 ! 非喜勿进 ! 本博文将带领你从入门到精通爬虫框架Scrapy,最终具备爬取任何网页的数据的能力.本文以校花网为例进行爬取,校花网:http://www.xiaohuar.com/,让你体验爬取校花的成就感. Scrapy,Python开发的一个快速,高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据.Scrapy用途广泛,可以用于数据挖掘.监测和自动化测试. Scrapy吸引人的地方在于

Cobra —— 可视化Python虚拟机

http://blog.csdn.net/balabalamerobert http://blog.csdn.net/efeics/article/category/1486515  图解python <Python源码剖析>与Cobra开源项目 2008-07-28 15:52 来自 Hailie Robert: 为了让这本书的阅读变得更有趣,也为了帮助读者更好地利用这本书,我在Google Code上发起了一个旨在可视化Python虚拟机的开源项目——Cobra(http://code.g

流行的python服务器框架有哪些

今天给大家推荐5款目前比较流行的python开发http://www.maiziedu.com/course/python/服务器框架,并向大家简单介绍下服务器框架的作用吧: 1.tonardo---- 多并发.轻量级应用, "非阻塞"的web 容器.类似tomcat.这个大家太熟悉了,就不多说了. 2.Twisted---- Twisted 是一个Python 应用程序和库文件的集成套件.其中包括全套页面服务器应用程序和基于文本模式的游戏引擎.还有一些诸如对数据流进行处理的模块.是一

《python源码剖析》笔记 python虚拟机中的一般表达式

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.字节码指令 LOAD_CONST:从consts表中读取序号为i的元素并压入到运行时栈中 STORE_NAME:改变local名字空间.从符号表names取序号为i的元素作为变量名, 取运行时栈的栈顶元素作为变量值,完成从变量名到变量值的映射关系的创建. BUILD_MAP:创建一个空的PyDictObject对象,并压入运行时栈 DUP_TOP:将栈顶元素的引用计数增加1,并将它再次

Python web 框架 Sanci如何使用?

本文和大家分享的主要是python web 框架 Sanci 相关内容,一起来看看吧,希望对大家学习python有所帮助. Sanic 是一个和类Flask 的基于Python3.5+的web框架,它编写的代码速度特别快. 除了像Flask 以外,Sanic 还支持以异步请求的方式处理请求.这意味着你可以使用新的 async/await 语法,编写非阻塞的快速的代码. 既然它说速度特别快,我们先看下官方提供的 基准测试结果. Sanic基准测试 这个测试的程序运行在 AWS 实例上,系统是Ubu