Python虚拟机框架这一章中,我们通过PyEval_EvalFrameEx看到了Python虚拟机的整体框架。而这章开始,我们将了解Python虚拟机是如何完成对Python的一般表达式的执行,这里的“一般表达式”包括最基本的对象创建语句,打印语句。至于if、while等表达式,我们将之归类于控制流语句,将再后面的章节介绍

简单内建对象的创建

我们先来看一段简单的对象创建语句:

demo.py

i = 1
s = "Python"
d = {}
l = []

  

上面的语句很简单,创建i、s、d、l四个变量,分别赋值为1、"Python"、字典、列表,现在,让我们看一下这个脚本所对应的PyCodeObject对象中的符号表co_names和常量表co_consts都包含了什么

# python2.5
……
>>> source = open("demo.py").read()
>>> co = compile(source, "demo.py", "exec")
>>> co.co_consts
(1, 'Python', None)
>>> co.co_names
('i', 's', 'd', 'l')

  

符号表和常量表保存着程序运行的重要信息,在Python虚拟机执行字节码指令时具有非常重要的作用

接下来,我们再用dis模块看一下demo.py对应的字节码

>>> import dis
>>> dis.dis(co)
1 0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (i) 2 6 LOAD_CONST 1 ('Python')
9 STORE_NAME 1 (s) 3 12 BUILD_MAP 0
15 STORE_NAME 2 (d) 4 18 BUILD_LIST 0
21 STORE_NAME 3 (l)
24 LOAD_CONST 2 (None)
27 RETURN_VALUE

  

最左边的一列是字节码指令在源代码中所对应的行数,左起第二列是当前字节码在co_code中的偏移位置,第三列显示了当前字节码的指令,第四列是指令的参数,最后一列是计算后的实际参数

在PyEval_EvalFrameEx的实现中,出于对效率的考虑,使用了大量的宏,其中的一些宏包括了对栈的各种操作以及对tupple元素的访问操作,在执行字节码指令时,会大量使用这些宏:

ceval.c

//访问tupple中的元素
#define GETITEM(v, i) PyTuple_GetItem((v), (i))
//调整栈顶指针
#define BASIC_STACKADJ(n) (stack_pointer += n)
#define STACKADJ(n) BASIC_STACKADJ(n)
//入栈操作
#define BASIC_PUSH(v) (*stack_pointer++ = (v))
#define PUSH(v) BASIC_PUSH(v)
//出栈操作
#define BASIC_POP() (*--stack_pointer)
#define POP() BASIC_POP()

  

图1-1

图1-1左边的stack_pointer是运行时栈的栈顶指针,字节码指令对符号和常量的操作最终都将反应到运行时栈和local名字空间(f->f_locals)

我们对demo.py结合dis所分析的结果逐行解析

i = 1
//分析结果
0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (i)

  

i = 1产生了两条字节码:LOAD_CONST和STORE_NAME,我们现在看下LOAD_CONST在PyEval_EvalFrameEx函数中的定义:

ceval.c

case LOAD_CONST:
x = GETITEM(consts, oparg);
Py_INCREF(x);
PUSH(x);
goto fast_next_opcode;

  

根据我们之前的定义,GETITEM(consts, oparg)显然就是GETITEM(consts, 0),即PyTuple_GetItem(consts, 0)。LOAD_CONST的意图很明显,就是从consts中读取序号为0的元素,然后再执行PUSH字节码将其压入运行时栈stack_pointer,其中,consts就是f->f_code->co_consts,其中,f是当前活动的PyFrameObject对象,那么consts也就是PyCodeObject对象中的co_consts

根据dis模块对demo.py的解析,我们可以知道consts的第0个元素是一个整数对象1,这也是demo.py中所创建的第一个对象。LOAD_CONST完成后运行时栈和名字空间如下图所示:

图1-2

第一条字节码指令LOAD_CONST只改变了运行时栈,对local名字空间没有任何影响。但别忘了,完成i = 1除了LOAD_CONST这条指令,还有一条STORE_NAME指令,STORE_NAME将完成在local名字空间中,实现符号i到PyIntObject对象1之间的映射关系,这样以后如果我们需要符号i,就可以到local名字空间查找i所对应的对象。现在,我们再来看一下STORE_NAME字节码的实现

ceva.lc

case STORE_NAME:
w = GETITEM(names, oparg);
v = POP();
if ((x = f->f_locals) != NULL) {
if (PyDict_CheckExact(x))
err = PyDict_SetItem(x, w, v);
else
err = PyObject_SetItem(x, w, v);
Py_DECREF(v);
if (err == 0) continue;
break;
}
PyErr_Format(PyExc_SystemError,
"no locals found when storing %s",
PyObject_REPR(w));
break;

  

这里,我们只考虑f->f_locals是PyDictObject对象的情况,代码会先取出符号表的符号,并从运行时栈中取出符号所对应的值,在PyDictObject这个对象中建立映射关系,而根据上面dis对i = 1的解析,可以发现STORE_NAME取出的符号确实是i。而完成STORE_NAME这一指令后,运行时栈和local名字空间的分部变为如下:

图1-3

  

而demo.py中的s = "Python"所对应的字节码与i = 1所对应的一模一样,这里不再做阐述。

现在,我们再来看一下demo.py的第三行d = {},是如何创建一个字典对象

d = {}
//分析结果
3 12 BUILD_MAP 0
15 STORE_NAME 2 (d)

  

指令BUILD_MAP会创建一个PyDictObject对象,并将之压入栈

ceval.c

case BUILD_MAP:
x = PyDict_New();
PUSH(x);
if (x != NULL) continue;
break;

  

可能有人会想,如果在声明字典时不单单声明一个空字典,而是填入参数呢?如:d = {"1": 1, "2": 2},不要急,关于这样的字典对应的字节码是如何生成并执行的,后面还会再介绍,再执行完BUILD_MAP和STORE_NAME之后,我们再来看下运行时栈和名字空间:

图1-4

再来看一下demo.py最后一行代码l = [],我们看一下它的分析结果:

l = []
//分析结果
4 18 BUILD_LIST 0
21 STORE_NAME 3 (l)
24 LOAD_CONST 2 (None)
27 RETURN_VALUE

  

BUILD_LIST这个字节码比BUILD_MAP稍微好点,因为它不像BUILD_MAP那样创建一个空字典就直接压入栈,而是会根据列表中的元素生成一个列表

ceval.c

case BUILD_LIST:
x = PyList_New(oparg);
if (x != NULL) {
for (; --oparg >= 0;) {
w = POP();
PyList_SET_ITEM(x, oparg, w);
}
PUSH(x);
continue;
}
break;

  

这里我们可以做一个猜测,如果BUILD_LIST创建的不是一个空列表,那在之前一定有若干LOAD_CONST操作,这将导致若干元素压入运行时栈中,在执行BUILD_LIST时,这些元素又会从运行时栈中弹出,加入新创建的PyListObject对象中。最后,执行STORE_NAME,完成符号与栈中列表的映射。现在,运行时栈和名字空间的分布应该如下:

图1-5

到这里,似乎demo.py所有的代码都执行完毕,但似乎我们还漏了些什么?在创建列表并建立映射之后,还有两句字节码:

24 LOAD_CONST    2 (None)
27 RETURN_VALUE

  

既然对象都已经创建完毕,那么多出的这两句又有什么用呢?原来,Python在执行完一段Code Block之后,一定要返回一些值,这条字节码指令就是用来返回这些值的,LOAD_CONST将None这个对象压入运行时栈,再在RETURN_VALUE时将栈中的对象,也就是None返回

ceval.c

case RETURN_VALUE:
retval = POP();
why = WHY_RETURN;
goto fast_block_end;

  

Python虚拟机中的一般表达式(一)的更多相关文章

  1. Python虚拟机中的一般表达式(三)

    其他一般表达式 在前两章:Python虚拟机中的一般表达式(一).Python虚拟机中的一般表达式(二)中,我们介绍了Python虚拟机是怎样执行创建一个整数值对象.字符串对象.字典对象和列表对象.现 ...

  2. Python虚拟机中的一般表达式(二)

    复杂内建对象的创建 在上一章Python虚拟机中的一般表达式(一)中,我们看到了Python是如何创建一个空的字典对象和列表对象,那么如果创建一个非空的字典对象和列表对象,Python的行为又是如何呢 ...

  3. 《python解释器源码剖析》第10章--python虚拟机中的一般表达式

    10.0 序 上一章中,我们通过PyEval_EvalFrameEx看到了python虚拟机的整体框架,那么这一章我们将深入到PyEval_EvalFrameEx的各个细节当中,深入剖析python的 ...

  4. (Python学习9)Python虚拟机中的一般表达式

    1.准备工作 执行.py程序时,Python解释器对PyCodeObject的co_code存储的字节码进行解释执行,同时co_consts存储了常量,co_names存储了变量名称.用compile ...

  5. 《python解释器源码剖析》第11章--python虚拟机中的控制流

    11.0 序 在上一章中,我们剖析了python虚拟机中的一般表达式的实现.在剖析一遍表达式是我们的流程都是从上往下顺序执行的,在执行的过程中没有任何变化.但是显然这是不够的,因为怎么能没有流程控制呢 ...

  6. python虚拟机中的异常流控制

    异常:对程序运行中的非正常情况进行抽象.并且提供相应的语法结构和语义元素,使得程序员能够通过这些语法结构和语义元素来方便地描述异常发生时的行为. 1.Python中的异常机制: 1.1Python虚拟 ...

  7. 《python解释器源码剖析》第13章--python虚拟机中的类机制

    13.0 序 这一章我们就来看看python中类是怎么实现的,我们知道C不是一个面向对象语言,而python却是一个面向对象的语言,那么在python的底层,是如何使用C来支持python实现面向对象 ...

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

    12.0 序 函数是任何一门编程语言都具备的基本元素,它可以将多个动作组合起来,一个函数代表了一系列的动作.当然在调用函数时,会干什么来着.对,要在运行时栈中创建栈帧,用于函数的执行. 在python ...

  9. 《python源代码剖析》笔记 python虚拟机中的函数机制

    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.Python虚拟机在运行函数调用时会动态地创建新的 PyFrameObject对象, 这 ...

随机推荐

  1. js页面可视区域懒加载

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. 十六进制和ASCII之间的转换

    2.关于两个byte[]数组的合并: public static byte[] byteMerger(byte[] byte_1, byte[] byte_2) { byte[] byte_3 = n ...

  3. I/O流操做总结(三)

    说实话,其实我并不是很喜欢Java这门语言,尽管它很强大,有很多现成的API可以调用 但我总感觉它把简单的事情弄得太过复杂,甚至有时候会让人迷失 弄不清到底是为了写出东西,还是为了语言本身 我学习的第 ...

  4. html学习笔记-XML

    html学习笔记-XML Table of Contents 1. XML简介 2. XML用途 3. XML树结构 4. XML语法 5. XML元素 6. XML属性 7. XML验证 8. XM ...

  5. css hack 浏览器携带自身特有的属性 (二)

    css hack 浏览器携带自身特有的属性,才是我们真正要解决的css 兼容问题. 这里只是分享思路. 举例子: 1 outline,尤其是一些 自带继承特性的属性.这里指的是 隐性的inherite ...

  6. Android安卓电话拦截及短信过滤

    package com.focus.manager; import java.lang.reflect.Method; import Android .app.Activity; import And ...

  7. npm使用快速的安装源(nrm)

    安装 npm install nrm --global 使用 nrm ls 切换安装源 nrm use taobao 测速 nrm test npm 参考地址:http://codingdict.co ...

  8. SingletonLoginUser

    package cn.com.jgt.view{ import flash.errors.IllegalOperationError; /** * actionscript类的构造方法不能是priva ...

  9. 如何从Ubuntu 16.04 LTS升级到Ubuntu 18.04 LTS

    可以说非常简单(假设过程顺利!!) 您只需打开Software&Update,进入"Updates"选项卡,然后从“有新版本时通知我”下拉菜单中选择“适用长期支持版”选项. ...

  10. Array - Merge Sorted Array

    /** * 将nums2中的值合并入nums1,使其仍然有序 * 可以任务nums1的长度>=m+n * @param nums1 已排序数组 * @param m nums1数组已初始化的数目 ...