Python虚拟机之while循环控制结构(三)
Python虚拟机中的while循环控制结构
在Python虚拟机之if控制流(一)和Python虚拟机之for循环控制流(二)两个章节中,我们介绍了if和for两个控制结构在Python虚拟机中的实现,但是这里还差一个while循环控制结构。在这里,我们不单单要考虑循环本身的指令跳跃动作,还要考虑另外两个与循环相关的指令跳跃语义:continue和break
下面,让我们看一下demo4.py这个程序,并用dis模块解释其对应的字节码
# cat demo4.py
i = 0
while i < 10:
i += 1
if i > 5:
continue
if i == 20:
break
print(i)
# python2.5
……
>>> source = open("demo4.py").read()
>>> co = compile(source, "demo4.py", "exec")
>>> import dis
>>> dis.dis(co)
1 0 LOAD_CONST 0 (0)
3 STORE_NAME 0 (i) 2 6 SETUP_LOOP 71 (to 80)
>> 9 LOAD_NAME 0 (i)
12 LOAD_CONST 1 (10)
15 COMPARE_OP 0 (<)
18 JUMP_IF_FALSE 57 (to 78)
21 POP_TOP 3 22 LOAD_NAME 0 (i)
25 LOAD_CONST 2 (1)
28 INPLACE_ADD
29 STORE_NAME 0 (i) 4 32 LOAD_NAME 0 (i)
35 LOAD_CONST 3 (5)
38 COMPARE_OP 4 (>)
41 JUMP_IF_FALSE 7 (to 51)
44 POP_TOP 5 45 JUMP_ABSOLUTE 9
48 JUMP_FORWARD 1 (to 52)
>> 51 POP_TOP 6 >> 52 LOAD_NAME 0 (i)
55 LOAD_CONST 4 (20)
58 COMPARE_OP 2 (==)
61 JUMP_IF_FALSE 5 (to 69)
64 POP_TOP 7 65 BREAK_LOOP
66 JUMP_FORWARD 1 (to 70)
>> 69 POP_TOP 8 >> 70 LOAD_NAME 0 (i)
73 PRINT_ITEM
74 PRINT_NEWLINE
75 JUMP_ABSOLUTE 9
>> 78 POP_TOP
79 POP_BLOCK
>> 80 LOAD_CONST 5 (None)
83 RETURN_VALUE
在执行"15 COMPARE_OP 0"之前,我们先来看一下运行时栈和名字空间的情况,字节码指令分别在执行"3 STORE_NAME 0"时在名字空间上将符号i和PyIntObject对象0进行映射,在执行"9 LOAD_NAME 0"和"12 LOAD_CONST 1 (10)"分别0和10压入运行时栈:
图1-2执行"15 COMPARE_OP 0"之前运行时栈和名字空间的情况
接着,"15 COMPARE_OP 0"指令将执行<比较操作,并将比较结果压入运行时栈中。这里,我们先来一个思维的跳跃,直接考虑循环结束时的情况,当某个时刻i的值开始大于或等于10,"15 COMPARE_OP 0"指令运行后的运行时栈和local名字空间如图1-2
图1-2循环结束时虚拟机的状态
虚拟机在执行紧接着的"18 JUMP_IF_FALSE 57"时,会发生指令跳跃的动作,跳跃的距离是57到偏移78的位置,根据dis模块的分析结果,这个距离正好是倒数第四条"78 POP_TOP"处,显然这条指令将Py_False弹出运行时栈,随后的虚拟机通过执行POP_BLOCK指令销毁PyTryBlock对象
循环的正常运转
另一方面,如果"15 COMPARE_OP 0"指令处的判断结果为Py_True,那么虚拟机将进入while循环。这里我们不考虑continue和break的情况(实际上break永远不会发生),只考虑循环正常运转时的情况。在循环正常运转时,i的值小于10,且不等于5,那么Python虚拟机接下来的动作及状态转换如图1-3所示:
图1-3循环运转过程中Python虚拟机的状态转换
将i的值递增之后,Python虚拟机通过PRINT_ITEM、PRINT_NEWLINE指令将i值输出到标准输出,然后通过执行指令"75 JUMP_ABSOLUTE 9"进行指令回退,回退的位置是距离字节码开始处的9个字节,正好是"while i < 10:"下面的"9 LOAD_NAME 0"指令。Python虚拟机又开始了新一轮的循环,继续比较i和10的大小,如此反复,在每一次TRUE分支中,都会使i的值递增1,直到i的值等于10。这时程序的执行流程就会转入False分支退出循环,然后Python虚拟机才会执行while循环控制结构之后的字节码指令序列
循环流程改变指令之continue
在demo4.py的while循环中,但i大于或等于5时,意味着"41 JUMP_IF_FALSE 7"指令的判断操作的结果为Py_True,那么这里的指令跳跃动作将不会发生,所以虚拟机将执行接下来的"44 POP_TOP"和"45 JUMP_ABSOLUTE 9"指令。在执行JUMP_ABSOLUTE指令时,虚拟机的流程跳转到"9 LOAD_NAME 0"指令处,这完全符合了continue的语义
循环流程改变指令之break
虽然我们在demo4.py的循环中设置了一条break语句,但实际上这条语句永远不会执行,因为在满足执行break语句的条件时(即i == 20),循环在i大于或等于10时便结束了。虽然break语句无法执行,但我们还是可以根据它执行时动作,看看break是如何跳出当前循环的
虚拟机在执行"61 JUMP_IF_FALSE 5"指令时,如果其中的判断操作的结果为Py_True,就意味着i == 20这个条件满足了,程序流程就进入了对break的执行。Python虚拟机首先会执行"64 POP_TOP"指令,将位于运行时栈栈顶的Py_True弹出,然后执行"65 BREAK_LOOP"指令结束循环
ceval.c
case BREAK_LOOP:
why = WHY_BREAK;
goto fast_block_end;
Python虚拟机将结束状态why设置为why_break,然后进入fast_block_end。fast_block_end是一段比较复杂的代码块,其中还有关于异常机制的代码,这里我们只截取break相关的代码:
ceval.c
#define JUMPTO(x) (next_instr = first_instr + (x)) fast_block_end:
while (why != WHY_NOT && f->f_iblock > 0)
{
//取得与当前while循环对应的PyTryBlock
PyTryBlock *b = PyFrame_BlockPop(f);
……
//将运行时栈恢复到while循环之前的状态
while (STACK_LEVEL() > b->b_level)
{
v = POP();
Py_XDECREF(v);
}
//处理break语义动作
if (b->b_type == SETUP_LOOP && why == WHY_BREAK)
{
why = WHY_NOT;
JUMPTO(b->b_handler);
break;
}
……
}
Python虚拟机首先获得了之前通过SETUP_LOOP指令申请得到的,与当前while循环对应的PyTryBlock结构,然后根据其中存储的运行时栈信息将运行时栈恢复到while循环之后的状态。最后Python虚拟机将why设置为WHY_NOT,表示退出状态没有任何错误,再通过JUMPTO宏,将虚拟机中下一条指令的指示器next_instr设置为距离code开始位置b->b_handler个字节的指令
这个b_handler是在执行SETUP_LOOP指令时设置的,参考SETUP_LOOP的指令代码和PyFrame_BlockSetup的代码可以看到,这个值会被设置为INSTR_OFFSET() + oparg。注意到这里的oparg是指令"6 SETUP_LOOP 71"的指令参数,即71。INSTR_OFFSET()宏对应的代码为((int)(next_instr - first_instr)),因为在执行SETUP_LOOP指令时,next_instr已经指向了下一条待执行的字节码指令,即"9 LOAD_NAME 0",很显然,这里的b_handler的值为INSTR_OFFSET() + oparg = 9 + 71 = 80。这也意味着break语句将导致Python虚拟机的流程跳转到前面所示的指令序列的倒数第2条指令"80 LOAD_CONST 3"处。确实,它已经跳出while循环所对应的指令序列了
值的注意的是,这里虽然使用了why这个用于栈帧(PyFrameObject)结束时的结束状态,但是实际上并没有结束当前活动的栈帧,而仅仅是利用其实现了break语义。可以看到,最后why又被设置为正常状态的WHY_NOT,而虚拟机仍然在当前栈帧中运行
Python虚拟机之while循环控制结构(三)的更多相关文章
- Python虚拟机之for循环控制流(二)
Python虚拟机中的for循环控制流 在Python虚拟机之if控制流(一)这一章中,我们了解if控制流的字节码实现,在if控制结构中,虽然Python虚拟机会在不同的分支摇摆,但大体还是向前执行, ...
- Python虚拟机之异常控制流(四)
Python虚拟机中的异常控制流 先前,我们分别介绍了Python虚拟机之if控制流(一).Python虚拟机之for循环控制流(二)和Python虚拟机之while循环控制结构(三).这一章,我们来 ...
- Python 全栈开发三 python基础 条件与循环
一. 条件语句 python条件语句是根据一条或多条语句的执行结果的真假(True Or False)来决定代码块的执行. 而执行内容可以多行,以缩进来区分表示同一范围. 1.Python判断条件真假 ...
- Python虚拟机中的一般表达式(三)
其他一般表达式 在前两章:Python虚拟机中的一般表达式(一).Python虚拟机中的一般表达式(二)中,我们介绍了Python虚拟机是怎样执行创建一个整数值对象.字符串对象.字典对象和列表对象.现 ...
- Python虚拟机类机制之descriptor(三)
从slot到descriptor 在Python虚拟机类机制之填充tp_dict(二)这一章的末尾,我们介绍了slot,slot包含了很多关于一个操作的信息,但是很可惜,在tp_dict中,与__ge ...
- Python虚拟机函数机制之参数类别(三)
参数类别 我们在Python虚拟机函数机制之无参调用(一)和Python虚拟机函数机制之名字空间(二)这两个章节中,分别PyFunctionObject对象和函数执行时的名字空间.本章,我们来剖析一下 ...
- 《python解释器源码剖析》第11章--python虚拟机中的控制流
11.0 序 在上一章中,我们剖析了python虚拟机中的一般表达式的实现.在剖析一遍表达式是我们的流程都是从上往下顺序执行的,在执行的过程中没有任何变化.但是显然这是不够的,因为怎么能没有流程控制呢 ...
- 《python源代码剖析》笔记 python虚拟机中的函数机制
本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 1.Python虚拟机在运行函数调用时会动态地创建新的 PyFrameObject对象, 这 ...
- Python虚拟机函数机制之位置参数的默认值(五)
位置参数的默认值 在Python中,允许函数的参数有默认值.假如函数f的参数value的默认值是1,在我们调用函数时,如果传递了value参数,那么f调用时value的值即为我们传递的值,如果调用时没 ...
随机推荐
- Java中的构造函数——通过示例学习Java编程(14)
作者:CHAITANYA SINGH 来源:https://www.koofun.com//pro/kfpostsdetail?kfpostsid=25 构造函数是用来初始化新创建的对象的代码块. ...
- R17下maps新增参数的问题
今天遇到一个奇怪的问题,我之前写的一个函数在我弟弟的机器上编译出错.代码如下: %%将list [k1,v1,k2,v2...]转换成map {k1=>v1,key2=>v2...} -s ...
- sublime text 快捷键新建.vue
第一步:添加模板: 模板写法如下: <template> </template> <script type="ecmascript-6"> &l ...
- 【Unity3D】魔鬼与牧师游戏记录——MVC架构
Priests and Devils是一款益智类的小游戏,需要在规定的时间内帮助牧师和魔鬼都安全过河.河边有三个魔鬼和三个牧师,他们都想过河,但河上只有一条船,这艘船每次只能搭载两个,而且必须有一个人 ...
- edittext 设置不自动获取焦点
给父级控件 设置两个属性,可以把焦点抢夺过去,最好是没有任何事件的父级控件(本人比较喜欢在xml文件的跟布局设置,因为页面的跟布局一般情况下,是不会设置任何事件的) android:focusable ...
- PHP的socket通信原理及实现
对TCP/IP.UDP.Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵.那么我想问: 1. 什么是TCP/IP.UDP?2. Sock ...
- SQL Server 2016,2014 “无法找到数据库引擎启动句柄”
当我决定安装SharePoint 2016 IT预览版时,我想我应该将它安装在Windows Server 2016技术预览版以及SQL Server 2016社区技术预览版(CTP)上.我敢打赌,你 ...
- dataset datatable datacolums datarow
DataSet 表示数据在内存中的缓存. 属性 Tables 获取包含在 DataSet 中的表的集合. ds.Tables["sjxx"] DataTable 表示内存中数据的 ...
- 地址栏传值 JS取值方法
function GetQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&] ...
- HDU 1864 最大报销额(01背包,烂题)
题意:被坑惨,单项不能超过600,其实是一张发票上A类/B类/C类的总和分别不能超过600. 思路:此题的数据很烂.用贪心也能过,用01背包也可以.都测试不出到底那些是错的. #include < ...