大师兄的Python源码学习笔记(十五): 虚拟机中的控制流(二)
大师兄的Python源码学习笔记(十七): 虚拟机中的控制流(四)
三、虚拟机中的while循环控制流
- 在Python3.7中只有三种基本控制结构:if、for和while(Python3.10中会加入switch)。
- 先写一段简单的代码:
demo.py
i = 0
while i<5:
i+=1
- 生成的字节码指令序列如下:
1 0 LOAD_CONST 0 (0)
2 STORE_NAME 0 (i)
2 4 SETUP_LOOP 20 (to 26)
>> 6 LOAD_NAME 0 (i)
8 LOAD_CONST 1 (5)
10 COMPARE_OP 0 (<)
12 POP_JUMP_IF_FALSE 24
3 14 LOAD_NAME 0 (i)
16 LOAD_CONST 2 (1)
18 INPLACE_ADD
20 STORE_NAME 0 (i)
22 JUMP_ABSOLUTE 6
>> 24 POP_BLOCK
>> 26 LOAD_CONST 3 (None)
28 RETURN_VALUE
1. 循环初始化
- 与for循环类似,虚拟机在SETUP处从当前活动的对象中申请了一块PyTryBlock空间,并填入一些当前的虚拟机状态正式开始循环。
4 SETUP_LOOP 20 (to 26)
>> 6 LOAD_NAME 0 (i)
8 LOAD_CONST 1 (5)
10 COMPARE_OP 0 (<)
12 POP_JUMP_IF_FALSE 24
2. 循环终止
- 当虚拟机执行到COMPARE_OP时,会将比较的结果存放到运行时栈中(比较过程参考if控制流)。
- 紧接着到POP_JUMP_IF_FALSE处,如果栈中的储存结果为False,则执行跳跃动作到POP_BLOCK。
- 在for循环控制流中我们知道POP_BLOCK将销毁PyTryBlock对象,从而结束循环过程。
3. 循环正常运转
- 如果栈中的储存结果为True,则继续执行字节码指令,直到JUMP_ABSOLUTE。
14 LOAD_NAME 0 (i)
16 LOAD_CONST 2 (1)
18 INPLACE_ADD
20 STORE_NAME 0 (i)
22 JUMP_ABSOLUTE 6
- 我们再for循环控制流中曾经见过JUMP_ABSOLUTE指令,他使虚拟机实现了字节码的向后回退动作,并跳转到循环刚刚初始化的部分。
2 4 SETUP_LOOP 20 (to 26)
>> 6 LOAD_NAME 0 (i)
- 至此,完成了一次while循环控制流。
4. 循环流程改变指令continue
- continue是Python中的常用指令:
demo.py
i = 0
while i<5:
i += 1
if i==0:
continue
1 0 LOAD_CONST 0 (0)
2 STORE_NAME 0 (i)
2 4 SETUP_LOOP 30 (to 36)
>> 6 LOAD_NAME 0 (i)
8 LOAD_CONST 1 (5)
10 COMPARE_OP 0 (<)
12 POP_JUMP_IF_FALSE 34
3 14 LOAD_NAME 0 (i)
16 LOAD_CONST 2 (1)
18 INPLACE_ADD
20 STORE_NAME 0 (i)
4 22 LOAD_NAME 0 (i)
24 LOAD_CONST 0 (0)
26 COMPARE_OP 2 (==)
28 POP_JUMP_IF_FALSE 6
5 30 JUMP_ABSOLUTE 6
32 JUMP_ABSOLUTE 6
>> 34 POP_BLOCK
>> 36 LOAD_CONST 3 (None)
38 RETURN_VALUE
- 从上面的代码可以看到,在判断满足条件后:
26 COMPARE_OP 2 (==)
28 POP_JUMP_IF_FALSE 6
5 30 JUMP_ABSOLUTE 6
- 虚拟机执行了continue指令,即JUMP_ABSOLUTE,向后回退到LOAD_NAME指令处:
32 JUMP_ABSOLUTE 6
- 这与python中对continue的定义一致。
5. 循环流程改变指令break
- break指令可以跳出一层循环:
demo.py
while True:
break
2 0 SETUP_LOOP 6 (to 8)
3 >> 2 BREAK_LOOP
4 JUMP_ABSOLUTE 2
6 POP_BLOCK
>> 8 LOAD_CONST 0 (None)
10 RETURN_VALUE
- 从字节码指令可以看出,break指令对应BREAK_LOOP字节码指令。
- 而BREAK_LOOP字节码指令对应以下代码:
ceval.c
TARGET(BREAK_LOOP) {
why = WHY_BREAK;
goto fast_block_end;
}
- 在这段代码中,虚拟机首先设定跳出循环的原因为WHY_BREAK:
ceval.c
enum why_code {
WHY_NOT = 0x0001, /* No error */
WHY_EXCEPTION = 0x0002, /* Exception occurred */
WHY_RETURN = 0x0008, /* 'return' statement */
WHY_BREAK = 0x0010, /* 'break' statement */
WHY_CONTINUE = 0x0020, /* 'continue' statement */
WHY_YIELD = 0x0040, /* 'yield' operator */
WHY_SILENCED = 0x0080 /* Exception silenced by 'with' */
};
- 之后跳出一层循环:
ceval.c
fast_block_end:
assert(why != WHY_NOT);
/* Unwind stacks if a (pseudo) exception occurred */
while (why != WHY_NOT && f->f_iblock > 0) {
/* Peek at the current block. */
PyTryBlock *b = &f->f_blockstack[f->f_iblock - 1];
assert(why != WHY_YIELD);
if (b->b_type == SETUP_LOOP && why == WHY_CONTINUE) {
why = WHY_NOT;
JUMPTO(PyLong_AS_LONG(retval));
Py_DECREF(retval);
break;
}
/* Now we have to pop the block. */
f->f_iblock--;
if (b->b_type == EXCEPT_HANDLER) {
UNWIND_EXCEPT_HANDLER(b);
continue;
}
UNWIND_BLOCK(b);
if (b->b_type == SETUP_LOOP && why == WHY_BREAK) {
why = WHY_NOT;
JUMPTO(b->b_handler);
break;
}
if (why == WHY_EXCEPTION && (b->b_type == SETUP_EXCEPT
|| b->b_type == SETUP_FINALLY)) {
PyObject *exc, *val, *tb;
int handler = b->b_handler;
_PyErr_StackItem *exc_info = tstate->exc_info;
/* Beware, this invalidates all b->b_* fields */
PyFrame_BlockSetup(f, EXCEPT_HANDLER, -1, STACK_LEVEL());
PUSH(exc_info->exc_traceback);
PUSH(exc_info->exc_value);
if (exc_info->exc_type != NULL) {
PUSH(exc_info->exc_type);
}
else {
Py_INCREF(Py_None);
PUSH(Py_None);
}
PyErr_Fetch(&exc, &val, &tb);
/* Make the raw exception data
available to the handler,
so a program can emulate the
Python main loop. */
PyErr_NormalizeException(
&exc, &val, &tb);
if (tb != NULL)
PyException_SetTraceback(val, tb);
else
PyException_SetTraceback(val, Py_None);
Py_INCREF(exc);
exc_info->exc_type = exc;
Py_INCREF(val);
exc_info->exc_value = val;
exc_info->exc_traceback = tb;
if (tb == NULL)
tb = Py_None;
Py_INCREF(tb);
PUSH(tb);
PUSH(val);
PUSH(exc);
why = WHY_NOT;
JUMPTO(handler);
break;
}
if (b->b_type == SETUP_FINALLY) {
if (why & (WHY_RETURN | WHY_CONTINUE))
PUSH(retval);
PUSH(PyLong_FromLong((long)why));
why = WHY_NOT;
JUMPTO(b->b_handler);
break;
}
} /* unwind stack */
/* End the loop if we still have an error (or return) */
if (why != WHY_NOT)
break;
assert(!PyErr_Occurred());
} /* main loop */
assert(why != WHY_YIELD);
/* Pop remaining stack entries. */
while (!EMPTY()) {
PyObject *o = POP();
Py_XDECREF(o);
}
if (why != WHY_RETURN)
retval = NULL;
assert((retval != NULL) ^ (PyErr_Occurred() != NULL));