• 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,而虚拟机仍然在当前栈帧中运行

  • 相关阅读:
    菜根谭#317
    菜根谭#316
    菜根谭#315
    菜根谭#314
    菜根谭#313
    菜根谭#312
    菜根谭#311
    菜根谭#310
    菜根谭#309
    Matlab xpC启动盘
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/9498793.html
Copyright © 2020-2023  润新知