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


    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie


    1.
    Python虚拟机会从编译得到的PyCodeObject对象中依次读入每一条字节码指令。
    并在当前的上下文环境中运行这条字节码指令。


    Python虚拟机实际上是在模拟操作中运行文件的过程
    PyCodeObject对象中包括了字节码指令以及程序的全部静态信息,但没有包括
    程序执行时的动态信息——执行环境(PyFrameObject)


    2.Python源代码中的PyFrameObject

    typedef struct _frame{
    	PyObject_VAR_HEAD //"执行时栈"的大小是不确定的
    	struct _frame *f_back; //执行环境链上的前一个frame。非常多个PyFrameObject连接起来形成执行环境链表
    	PyCodeObject *f_code; //PyCodeObject 对象,这个frame就是这个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;


    名字空间实际上是维护着变量名和变量值之间关系的PyDictObject对象。
    f_builtins, f_globals, f_locals名字空间分别维护了builtin, global, local的name与相应值
    之间的映射关系。



    每个 PyFrameObject对象都维护了一个 PyCodeObject对象。这表明每个 PyFrameObject中的动态内存空间
    对象都和源码中的一段Code相相应。



    Code Block。PyFrameObject,作用域,名字空间好像都与一段代码段一一相应?



    PyFrameObject中的动态内存空间

    PyFrameObject *PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, PyCodeObject *locals)
    {
    	PyFrameObject *f;
    	Py_ssize_t extras, ncells, nfrees, i;
    	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;
    	f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras);
    	//计算初始化时执行时栈的栈顶
    	extras = code->co_nlocals + ncells + nfrees;
    	//f_valuestack维护执行时栈的栈底,f_stacktop维护执行时栈的栈顶
    	f->f_valuestack = f->f_localsplus + extras;
    	f->f_stacktop = f->f_valuestack;
    	return f;
    }
    在创建 PyFrameObject对象时,额外申请的内存有一部分是给 PyCodeObject对象用的,
    还有一部分才是给执行时栈用的。


    在Python中訪问 PyFrameObject对象
    8.1.3的caller.py我执行失败了。在f_globals.keys()处出错
    看不懂frame_getter.py

    3.名字、作用域和名字空间
    对于python这类动态语言来说,名字的意义远比C这类的静态语言大。由于名字是python
    在执行时可以找到其所相应的东西的唯一途径。



    名字: 一个标识符就是一个名字,比方变量名、函数名、类名等
    作用域:作用域是指约束起作用的程序正文区域。

    Python是具有静态作用域(词法作用域)的。
    在Python中,一个约束在程序正文的某个位置是否起作用。是由该约束在文本中的位置唯一决定的,
    而不是执行时动态决定的。


    名字空间:由一个 PyDictObject对象实现。每个名字空间都与一个作用域相应

    module
    1.将逻辑相关的代码放在一起,实现代码复用
    2.为系统划分名字空间(一个module定义了一个独立的名字空间)

    赋值语句会影响名字空间(“拥有赋值行为、拥有设置对象属性的行为”)
    1.创建一个对象obj
    2.将obj"赋给"一个名字name

    a = 1
    def f()
    class A(object)
    import abc

    
    嵌套作用域最内嵌套作用域规则:1.闭包实现  2.编译器实现LEGB规则:名字引用动作沿着local作用域、嵌套作用域、global作用域、builtin作用域的顺序查找函数相应local作用域。module源文件相应global作用域,Python自身定义了最顶层的作用域——builtin作用域几个有助理解LEGB规则的代码段
    <span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;"></span><pre name="code" class="cpp">#----------------------
    a = 1 #[1]
    def f():
    	a = 2 #[2]
    	print a #[3] 輸出2
    print a #[4] 輸出1
    #----------------------
    #闭包。

    在运行func = f()的时候。会运行函数f中的def g():语句,这时Python会将约束a = 2与函数g相应的函数对象捆绑在一起

    #将捆绑后的结果返回
    a = 1
    def f():
    	a = 2
    	def g():
    		print a #[1]:输出结果为2
    	return g
    func = f()
    func() #[2]
    #----------------------
    <pre name="code" class="cpp">#由一个赋值语句引进的名字在这个赋值语句所在的作用域是可见的。在 第9章 Python虚拟机中的一般表达式 这章有说明实现原理
    #[1]pint a语句处,a已经存在了这个Code Block相应的PyFrameObject相应的co_names符号表里了。但因为还没运行[2]的赋值语句,
    #在PyFrameObject的f_locals里并没有这个键值对,所以在[1]处会出现“local variable 'a' referenced before assignment”的错误
    a = 1def g():print adef f():print a #[1] error: local variable 'a' referenced before assignment a = 2 #[2]print ag()f()#----------------------a = 1def f():global aprint a #输出结果:1a = 2f()print a #输出结果:2#----------------------a = 1def f():a = 2def g():global aprint a #输出结果:1a += 1return gg = f()g()print a #输出结果:2
    
    
    
    
    4.Python虚拟机的执行框架——字节码执行引擎
    PyEval_EvalFrameEx 
    虚拟机的详细实现-->switch/case结构
    Python虚拟机运行字节码指令序列的过程就是从头到尾遍历整个co_code、依次运行字节码指令(字符数组)的过程。



    /* Interpreter main loop */ 
    PyObject* PyEval_EvalFrame(PyFrameObject *f) 
    {
    	//…… 
    	why = WHY_NOT;//指示了在退出for循环时Python运行引擎的状态
    	for (;;) {
    	//…… 
    	fast_next_opcode: 
    		f->f_lasti = INSTR_OFFSET();
    		/* 获得字节码指令和參数*/
    		opcode = NEXTOP();
    		oparg = 0;  
    		/* 假设指令须要參数。获得指令參数*/
    		if (HAS_ARG(opcode))
    			oparg = NEXTARG();
    	dispatch_opcode: 
    		switch (opcode) {
    		case NOP:
    		goto fast_next_opcode;
    		case LOAD_FAST:
    		//…… 
    		} 
    }

    5.Python执行时环境初探
    PyInterpreterState --> 进程的抽象
    PyThreadState --> 线程状态信息的抽象
    在Win32下,线程是还能独立存活的,它须要存活在进程的环境中。多个线程能够共享进程的一些资源
    进程中会有线程集合,线程中会有函数调用堆栈。


    当Python虚拟机開始运行时,会将当前线程状态对象中的frame设置为当前的运行环境(frame),而
    在建立新的PyFrameObject对象时,则从当前线程的状态对象中取出旧的frame,建立PyFrameObject链表
    线程之间的同步靠的是全局解释锁(GIL)

    //进程
    typedef struct _is{
    	struct _is *next;
    	struct _ts *tstate_head; //模拟进程环境中的线程集合
    	
    	PyObject *modules;
    	PyObject *sysdict;
    	PyObject *builtins;
    	
    	//...
    } PyInterpreterState
    //线程
    typedef struct _ts{
    	struct _ts *next
    	PyInterpreterState *interp;
    	struct _frame *frame; //模拟线程中的函数调用堆栈
    	int recursion_depth;
    	//...
    	PyObject *dict;
    	//...
    	long thread_id;
    } PyThreadState;


  • 相关阅读:
    app ios info权限配置:
    ionic3 小记录
    mipush ionic3 线上push
    ionic3 生命周期 之 ionViewWillLeave 坑
    iphone X 底部留白 之 ionic3 项目
    微信小程序 修改手机状态栏颜色
    git 命令
    微信小程序 下拉加载
    js 判断浏览器型号
    关于 ionic3 tabs 导航ico 点击 页面返回顶部
  • 原文地址:https://www.cnblogs.com/yutingliuyl/p/6872864.html
Copyright © 2020-2023  润新知