当前位置:   article > 正文

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

python 虚拟机原码

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


1.
Python虚拟机会从编译得到的PyCodeObject对象中依次读入每一条字节码指令,
并在当前的上下文环境中执行这条字节码指令。
Python虚拟机实际上是在模拟操作中执行文件的过程
PyCodeObject对象中包含了字节码指令以及程序的所有静态信息,但没有包含
程序运行时的动态信息——执行环境(PyFrameObject)


2.Python源码中的PyFrameObject

  1. typedef struct _frame{
  2. PyObject_VAR_HEAD //"运行时栈"的大小是不确定的
  3. struct _frame *f_back; //执行环境链上的前一个frame,很多个PyFrameObject连接起来形成执行环境链表
  4. PyCodeObject *f_code; //PyCodeObject 对象,这个frame就是这个PyCodeObject对象的上下文环境
  5. PyObject *f_builtins; //builtin名字空间
  6. PyObject *f_globals; //global名字空间
  7. PyObject *f_locals; //local名字空间
  8. PyObject **f_valuestack; //"运行时栈"的栈底位置
  9. PyObject **f_stacktop; //"运行时栈"的栈顶位置
  10. //...
  11. int f_lasti; //上一条字节码指令在f_code中的偏移位置
  12. int f_lineno; //当前字节码对应的源代码行
  13. //...
  14. //动态内存,维护(局部变量+cell对象集合+free对象集合+运行时栈)所需要的空间
  15. PyObject *f_localsplus[1];
  16. } PyFrameObject;


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

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

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



PyFrameObject中的动态内存空间
  1. PyFrameObject *PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals, PyCodeObject *locals)
  2. {
  3. PyFrameObject *f;
  4. Py_ssize_t extras, ncells, nfrees, i;
  5. ncells = PyTuple_GET_SIZE(code->co_cellvars);
  6. nfrees = PyTuple_GET_SIZE(code->co_freevars);
  7. //这四部分构成了 PyFrameObject维护的动态内存区,其大小由extras确定
  8. extras = code->co_stacksize + code->co_nlocals + ncells + nfrees;
  9. f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras);
  10. //计算初始化时运行时栈的栈顶
  11. extras = code->co_nlocals + ncells + nfrees;
  12. //f_valuestack维护运行时栈的栈底,f_stacktop维护运行时栈的栈顶
  13. f->f_valuestack = f->f_localsplus + extras;
  14. f->f_stacktop = f->f_valuestack;
  15. return f;
  16. }
在创建 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

  1. a = 1
  2. def f()
  3. class A(object)
  4. import abc

 嵌套作用域最内嵌套作用域规则:1.闭包实现  2.编译器实现LEGB规则:名字引用动作沿着local作用域、嵌套作用域、global作用域、builtin作用域的顺序查找函数对应local作用域,module源文件对应global作用域,Python自身定义了最顶层的作用域——builtin作用域几个有助理解LEGB规则的代码段 
  1. <span style="font-family: Arial, Helvetica, sans-serif; font-size: 12px;"></span><pre name="code" class="cpp">#----------------------
  2. a = 1 #[1]
  1. def f():
  2. a = 2 #[2]
  3. print a #[3] 輸出2
  4. print a #[4] 輸出1
  5. #----------------------
  6. #闭包。在执行func = f()的时候,会执行函数f中的def g():语句,这时Python会将约束a = 2与函数g对应的函数对象捆绑在一起
#将捆绑后的结果返回
a = 1
  1. def f():
  2. a = 2
  3. def g():
  4. print a #[1]:输出结果为2
  5. return g
  6. func = f()
  7. func() #[2]
  8. #----------------------
  1. <pre name="code" class="cpp">#由一个赋值语句引进的名字在这个赋值语句所在的作用域是可见的。在 第9章 Python虚拟机中的一般表达式 这章有说明实现原理
  2. #[1]pint a语句处,a已经存在了这个Code Block对应的PyFrameObject对应的co_names符号表里了,但由于还没执行[2]的赋值语句,
  3. #在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、依次执行字节码指令(字符数组)的过程。


  1. /* Interpreter main loop */
  2. PyObject* PyEval_EvalFrame(PyFrameObject *f)
  3. {
  4. //……
  5. why = WHY_NOT;//指示了在退出for循环时Python执行引擎的状态
  6. for (;;) {
  7. //……
  8. fast_next_opcode:
  9. f->f_lasti = INSTR_OFFSET();
  10. /* 获得字节码指令和参数*/
  11. opcode = NEXTOP();
  12. oparg = 0;
  13. /* 如果指令需要参数,获得指令参数*/
  14. if (HAS_ARG(opcode))
  15. oparg = NEXTARG();
  16. dispatch_opcode:
  17. switch (opcode) {
  18. case NOP:
  19. goto fast_next_opcode;
  20. case LOAD_FAST:
  21. //……
  22. }
  23. }

5.Python运行时环境初探
PyInterpreterState --> 进程的抽象
PyThreadState --> 线程状态信息的抽象
在Win32下,线程是还能独立存活的,它需要存活在进程的环境中,多个线程可以共享进程的一些资源
进程中会有线程集合,线程中会有函数调用堆栈。
当Python虚拟机开始执行时,会将当前线程状态对象中的frame设置为当前的执行环境(frame),而
在建立新的PyFrameObject对象时,则从当前线程的状态对象中取出旧的frame,建立PyFrameObject链表
线程之间的同步靠的是全局解释锁(GIL)
  1. //进程
  2. typedef struct _is{
  3. struct _is *next;
  4. struct _ts *tstate_head; //模拟进程环境中的线程集合
  5. PyObject *modules;
  6. PyObject *sysdict;
  7. PyObject *builtins;
  8. //...
  9. } PyInterpreterState
  10. //线程
  11. typedef struct _ts{
  12. struct _ts *next
  13. PyInterpreterState *interp;
  14. struct _frame *frame; //模拟线程中的函数调用堆栈
  15. int recursion_depth;
  16. //...
  17. PyObject *dict;
  18. //...
  19. long thread_id;
  20. } PyThreadState;


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/199220
推荐阅读
相关标签
  

闽ICP备14008679号