大師兄的Python源碼學(xué)習(xí)筆記(十五): 虛擬機(jī)中的控制流(二)
大師兄的Python源碼學(xué)習(xí)筆記(十七): 虛擬機(jī)中的控制流(四)
三、虛擬機(jī)中的while循環(huán)控制流
- 在Python3.7中只有三種基本控制結(jié)構(gòu):if、for和while(Python3.10中會加入switch)。
- 先寫一段簡單的代碼:
demo.py
i = 0
while i<5:
i+=1
- 生成的字節(jié)碼指令序列如下:
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. 循環(huán)初始化
- 與for循環(huán)類似,虛擬機(jī)在SETUP處從當(dāng)前活動的對象中申請了一塊PyTryBlock空間,并填入一些當(dāng)前的虛擬機(jī)狀態(tài)正式開始循環(huán)。
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. 循環(huán)終止
- 當(dāng)虛擬機(jī)執(zhí)行到COMPARE_OP時,會將比較的結(jié)果存放到運(yùn)行時棧中(比較過程參考if控制流)。
- 緊接著到POP_JUMP_IF_FALSE處,如果棧中的儲存結(jié)果為False,則執(zhí)行跳躍動作到POP_BLOCK。
- 在for循環(huán)控制流中我們知道POP_BLOCK將銷毀PyTryBlock對象,從而結(jié)束循環(huán)過程。
3. 循環(huán)正常運(yùn)轉(zhuǎn)
- 如果棧中的儲存結(jié)果為True,則繼續(xù)執(zhí)行字節(jié)碼指令,直到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循環(huán)控制流中曾經(jīng)見過JUMP_ABSOLUTE指令,他使虛擬機(jī)實現(xiàn)了字節(jié)碼的向后回退動作,并跳轉(zhuǎn)到循環(huán)剛剛初始化的部分。
2 4 SETUP_LOOP 20 (to 26)
>> 6 LOAD_NAME 0 (i)
- 至此,完成了一次while循環(huán)控制流。
4. 循環(huán)流程改變指令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
- 虛擬機(jī)執(zhí)行了continue指令,即JUMP_ABSOLUTE,向后回退到LOAD_NAME指令處:
32 JUMP_ABSOLUTE 6
- 這與python中對continue的定義一致。
5. 循環(huán)流程改變指令break
- break指令可以跳出一層循環(huán):
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
- 從字節(jié)碼指令可以看出,break指令對應(yīng)BREAK_LOOP字節(jié)碼指令。
- 而BREAK_LOOP字節(jié)碼指令對應(yīng)以下代碼:
ceval.c
TARGET(BREAK_LOOP) {
why = WHY_BREAK;
goto fast_block_end;
}
- 在這段代碼中,虛擬機(jī)首先設(shè)定跳出循環(huán)的原因為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' */
};
- 之后跳出一層循環(huán):
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));