Python虚拟机中的一般表达式(一)
在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虚拟机中的一般表达式(一)的更多相关文章
- Python虚拟机中的一般表达式(三)
其他一般表达式 在前两章:Python虚拟机中的一般表达式(一).Python虚拟机中的一般表达式(二)中,我们介绍了Python虚拟机是怎样执行创建一个整数值对象.字符串对象.字典对象和列表对象.现 ...
- Python虚拟机中的一般表达式(二)
复杂内建对象的创建 在上一章Python虚拟机中的一般表达式(一)中,我们看到了Python是如何创建一个空的字典对象和列表对象,那么如果创建一个非空的字典对象和列表对象,Python的行为又是如何呢 ...
- 《python解释器源码剖析》第10章--python虚拟机中的一般表达式
10.0 序 上一章中,我们通过PyEval_EvalFrameEx看到了python虚拟机的整体框架,那么这一章我们将深入到PyEval_EvalFrameEx的各个细节当中,深入剖析python的 ...
- (Python学习9)Python虚拟机中的一般表达式
1.准备工作 执行.py程序时,Python解释器对PyCodeObject的co_code存储的字节码进行解释执行,同时co_consts存储了常量,co_names存储了变量名称.用compile ...
- 《python解释器源码剖析》第11章--python虚拟机中的控制流
11.0 序 在上一章中,我们剖析了python虚拟机中的一般表达式的实现.在剖析一遍表达式是我们的流程都是从上往下顺序执行的,在执行的过程中没有任何变化.但是显然这是不够的,因为怎么能没有流程控制呢 ...
- python虚拟机中的异常流控制
异常:对程序运行中的非正常情况进行抽象.并且提供相应的语法结构和语义元素,使得程序员能够通过这些语法结构和语义元素来方便地描述异常发生时的行为. 1.Python中的异常机制: 1.1Python虚拟 ...
- 《python解释器源码剖析》第13章--python虚拟机中的类机制
13.0 序 这一章我们就来看看python中类是怎么实现的,我们知道C不是一个面向对象语言,而python却是一个面向对象的语言,那么在python的底层,是如何使用C来支持python实现面向对象 ...
- 《python解释器源码剖析》第12章--python虚拟机中的函数机制
12.0 序 函数是任何一门编程语言都具备的基本元素,它可以将多个动作组合起来,一个函数代表了一系列的动作.当然在调用函数时,会干什么来着.对,要在运行时栈中创建栈帧,用于函数的执行. 在python ...
- 《python源代码剖析》笔记 python虚拟机中的函数机制
本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.Python虚拟机在运行函数调用时会动态地创建新的 PyFrameObject对象, 这 ...
随机推荐
- SLF4J user manual 专题
System Out and Err Redirected to SLF4J The sysout-over-slf4j module allows a user to redirect all ca ...
- 开机报错 the connected AC adapter has a lower wattage than the recommended model which was shipped with the system。
机型:联想Thinkpad T410 报错场景:在电脑插上电源充电情况下开机,会自动进入bios setup utility提示你需要重新设置日期时间.date/time 报错提示:The conne ...
- 微信小程序中的target和currentTarget区别
最近在学习微信小程序相关知识,其中提到了两个属性target和currentTarget,其中target是指向触发事件的元素(常见于事件委托中),而currentTarget是指向捕获事件的元素(即 ...
- 通过Jenkins调用自动部署war包及jar包到服务器上的Shell脚本
1)部署war包#!/bin/bashif [ id>0];then echo"stopproject" kill −9 idelse echo "project ...
- [uva]AncientMessages象形文字识别 (dfs求连通块)
非常有趣的一道题目,大意是给你六种符号的16进制文本,让你转化成二进制并识别出来 代码实现上参考了//http://blog.csdn.net/u012139398/article/details/3 ...
- HTML5中的SSE(服务器推送技术)
本文原链接:https://cloud.tencent.com/developer/article/1194063 SSE技术详解:一种全新的HTML5服务器推送事件技术 前言 概述 基本介绍 与We ...
- QT 调试输出格式
Qt调试输出格式: 1,qDebug() << qPrintable(firstNode.nodeName()) << qPrintable(firstNode.nodeVal ...
- 使用Process组件访问本地进程
实现效果; 知识运用: Process组件的StartInfo属性 //获取或设置要传递给Process的Start方法的属性 public ProcessStartInfo StartInfo {g ...
- 常用的ement语法
缩写语法: 介绍:Emmet 使用类似于 CSS 选择器的语法描述元素在生成的文档树中的位置及其属性. 声明:第一次写博客大家多多关照,如有错误或者需要补充的请到评论里留言,谢谢大家! 快速生成htm ...
- Java中线程的通讯
线程间的相互作用:线程之间需要一些协调通信,来共同完成一件任务. Object类中相关的方法有两个notify方法和三个wait方法:因为wait和notify方法定义在Object类中,因此会被所有 ...