python 控制流程
简单分支控制
a = 0
if a > 10:
a = 10
else:
a = 0
1 0 LOAD_CONST 0 (0)
2 STORE_NAME 0 (a)
2 4 LOAD_NAME 0 (a)
6 LOAD_CONST 1 (10)
8 COMPARE_OP 4 (>)
10 POP_JUMP_IF_FALSE 18
3 12 LOAD_CONST 1 (10)
14 STORE_NAME 0 (a)
16 JUMP_FORWARD 4 (to 22)
5 >> 18 LOAD_CONST 0 (0)
20 STORE_NAME 0 (a)
>> 22 LOAD_CONST 2 (None)
24 RETURN_VALUE
None
- LOAD_CONST:从consts元组中取出变量0并压入栈中,变量0的位置在元组下标0处。
- STORE_NAME:从names元组中取出变量a并弹出栈顶元素,存入f_locals字典中。
- LOAD_NAME:按照f_locals,f_globals,f_builtin的搜索顺序搜索变量a,将搜索到的变量值压入栈中。
- LOAD_CONST:从consts元组中取出变量10并压入栈中,变量0的位置在元组下标1处。
COMPARE_OP:
case TARGET(COMPARE_OP): { assert(oparg <= Py_GE); PyObject *right = POP(); PyObject *left = TOP(); PyObject *res = PyObject_RichCompare(left, right, oparg); SET_TOP(res); Py_DECREF(left); Py_DECREF(right); if (res == NULL) goto error; PREDICT(POP_JUMP_IF_FALSE); PREDICT(POP_JUMP_IF_TRUE); DISPATCH(); }
- POP():弹出栈顶元素并获取它。
- TOP():获取栈顶元素但不弹出。
PyObject_RichCompare():
- 在函数中初步校验传入参数后,调用do_richcompare函数。
在do_richcompare函数中,函数首先校验两个参数的类型是否相同并判断类型的tp_richcompare是否指向了某个函数实现,若都为真,则调用tp_richcompare指向的函数。
if (!Py_IS_TYPE(v, Py_TYPE(w)) && PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) && (f = Py_TYPE(w)->tp_richcompare) != NULL) { checked_reverse_op = 1; res = (*f)(w, v, _Py_SwappedOp[op]); if (res != Py_NotImplemented) return res; Py_DECREF(res); }
若上述代码成功比较,则函数判断左侧变量是否实现了tp_richcompare,若实现了,则调用tp_richcompare。
if ((f = Py_TYPE(v)->tp_richcompare) != NULL) { res = (*f)(v, w, op); if (res != Py_NotImplemented) return res; Py_DECREF(res); }
如果函数没有进入第一个if,且右值的tp_richcompare有实现,若实现了则调用tp_richcompare函数进行比较。
if (!checked_reverse_op && (f = Py_TYPE(w)->tp_richcompare) != NULL) { res = (*f)(w, v, _Py_SwappedOp[op]); if (res != Py_NotImplemented) return res; Py_DECREF(res); }
如果left和right都没有实现tp_richcompare,python为==和!=提供默认的比较方法,如果不是==和!=,python将抛出一个警告。
switch (op) { case Py_EQ: res = (v == w) ? Py_True : Py_False; break; case Py_NE: res = (v != w) ? Py_True : Py_False; break; default: _PyErr_Format(tstate, PyExc_TypeError, "'%s' not supported between instances of '%.100s' and '%100s'", opstrings[op], Py_TYPE(v)->tp_name, Py_TYPE(w)->tp_name); return NULL; } Py_INCREF(res); return res;
- SET_TOP():设置栈顶元素,即left被更换为Py_True或Py_False对象。
PREDICT:指令预测,在python中,某些指令通常是成对出现的,如果下一条指令是预测的指令,则直接跳转到该指令执行,可以节省运行时间。由下一条指令可知,预测成功。PREDICT是宏函数,也可以节省运行时间。
#define PREDICT(op) \ do { \ _Py_CODEUNIT word = *next_instr; \ opcode = _Py_OPCODE(word); \ if (opcode == op) { \ oparg = _Py_OPARG(word); \ next_instr++; \ goto PREDICT_ID(op); \ } \ } while(0)
POP_JUMP_IF_FALSE:
case TARGET(POP_JUMP_IF_FALSE): { PREDICTED(POP_JUMP_IF_FALSE); PyObject *cond = POP(); int err; if (Py_IsTrue(cond)) { Py_DECREF(cond); DISPATCH(); } if (Py_IsFalse(cond)) { Py_DECREF(cond); JUMPTO(oparg); CHECK_EVAL_BREAKER(); DISPATCH(); } err = PyObject_IsTrue(cond); Py_DECREF(cond); if (err > 0) ; else if (err == 0) { JUMPTO(oparg); CHECK_EVAL_BREAKER(); } else goto error; DISPATCH(); }
- 如果判断比较的结果是TRUE,那就什么也不做。
- 如果判断比较的结果是FALSE,那将会执行
JUMPTO(oparg)
函数跳过部分指令码,oparg是传入的参数,代表指令码的绝对地址,JUMPTO是宏函数,#define JUMPTO(x) (next_instr = first_instr + (x))
。在这段代码中,如果比较为False,则跳转到字节码18处执行,即如果a<=10,则跳转else代码块执行。 如果栈中对象不是Py_True,Py_False对象,那么判断PyObject是否为真,即通常所说的list,dict是否为空,数字是否为0等。
int PyObject_IsTrue(PyObject *v) { Py_ssize_t res; if (v == Py_True) return 1; if (v == Py_False) return 0; if (v == Py_None) return 0; else if (Py_TYPE(v)->tp_as_number != NULL && Py_TYPE(v)->tp_as_number->nb_bool != NULL) res = (*Py_TYPE(v)->tp_as_number->nb_bool)(v); else if (Py_TYPE(v)->tp_as_mapping != NULL && Py_TYPE(v)->tp_as_mapping->mp_length != NULL) res = (*Py_TYPE(v)->tp_as_mapping->mp_length)(v); else if (Py_TYPE(v)->tp_as_sequence != NULL && Py_TYPE(v)->tp_as_sequence->sq_length != NULL) res = (*Py_TYPE(v)->tp_as_sequence->sq_length)(v); else return 1; /* if it is negative, it should be either -1 or -2 */ return (res > 0) ? 1 : Py_SAFE_DOWNCAST(res, Py_ssize_t, int); }
- LOAD_CONST:从consts元组中取出变量10并压入栈中,变量0的位置在元组下标1处。这是if代码块中的指令码。
- STORE_NAME:从names元组中取出变量a并弹出栈顶元素,存入f_locals字典中。这是if代码块中的指令码。
JUMP_FORWARD:直接向前跳转。参数是指令码的相对地址,相对正在执行的这条指令码的地址。此段代码的目的是跳过else代码块。
case TARGET(JUMP_FORWARD): { JUMPBY(oparg); DISPATCH(); }
#define JUMPBY(x) (next_instr += (x))
- LOAD_CONST:从consts元组中取出变量0并压入栈中,变量0的位置在元组下标0处。这是else代码块中的指令码。
- STORE_NAME:从names元组中取出变量a并弹出栈顶元素,存入f_locals字典中。这是else代码块中的指令码。
- 添加了elif的代码,代码基础逻辑不变。
复杂分支控制
a = 0
if a > 10 and a < 20:
a = 10
else:
a = 0
1 0 LOAD_CONST 0 (0)
2 STORE_NAME 0 (a)
2 4 LOAD_NAME 0 (a)
6 LOAD_CONST 1 (10)
8 COMPARE_OP 4 (>)
10 POP_JUMP_IF_FALSE 26
12 LOAD_NAME 0 (a)
14 LOAD_CONST 2 (20)
16 COMPARE_OP 0 (<)
18 POP_JUMP_IF_FALSE 26
3 20 LOAD_CONST 1 (10)
22 STORE_NAME 0 (a)
24 JUMP_FORWARD 4 (to 30)
6 >> 26 LOAD_CONST 0 (0)
28 STORE_NAME 0 (a)
>> 30 LOAD_CONST 3 (None)
32 RETURN_VALUE
None
- 指令码逻辑同简单分支控制,但是注意到指令码首先判断a>10,判断完成后才然后判断a<=20。也就是说,如果a>10为假则直接跳转到指令码地址26处,不会再判断a<20。
上述的代码其实不符合pep8规范,符合规范的代码为:
a = 0 if 10 < a < 20: a = 10 else: a = 0
1 0 LOAD_CONST 0 (0) 2 STORE_NAME 0 (a) 2 4 LOAD_CONST 1 (10) 6 LOAD_NAME 0 (a) 8 DUP_TOP 10 ROT_THREE 12 COMPARE_OP 0 (<) 14 POP_JUMP_IF_FALSE 24 16 LOAD_CONST 2 (20) 18 COMPARE_OP 0 (<) 20 POP_JUMP_IF_FALSE 34 22 JUMP_FORWARD 4 (to 28) >> 24 POP_TOP 26 JUMP_FORWARD 6 (to 34) 3 >> 28 LOAD_CONST 1 (10) 30 STORE_NAME 0 (a) 32 JUMP_FORWARD 4 (to 38) 5 >> 34 LOAD_CONST 0 (0) 36 STORE_NAME 0 (a) >> 38 LOAD_CONST 3 (None) 40 RETURN_VALUE None
- 注意到经过编译后得到的字节码发生了变化
DUP_TOP:复制栈顶元素
case TARGET(DUP_TOP): { PyObject *top = TOP(); Py_INCREF(top); PUSH(top); DISPATCH(); }
- 此时栈:[10,0,0] (左侧是栈底)
ROT_THREE:将栈中第二个元素放置在栈顶,第一个元素放置在第二个元素位置,栈顶元素放置在滴三个元素位置。
case TARGET(ROT_THREE): { PyObject *top = TOP(); PyObject *second = SECOND(); PyObject *third = THIRD(); SET_TOP(second); SET_SECOND(third); SET_THIRD(top); DISPATCH(); }
- 此时栈:[0, 10, 0] (左侧是栈底)
- COMPARE_OP:此时判断10 < 0,将结果写入栈。
- POP_JUMP_IF_FALSE:弹出栈顶,如果是假则跳跃,如果为假,跳跃到指令码地址为24处,执行POP_TOP指令弹出栈顶(因为之前复制了栈顶,应当将比较所产生的数清理掉),指令进入到else代码块;如果不为假则什么也不做。
- LOAD_CONST:将变量20压入栈。
- COMPARE_OP:比较0<20,将结果写入栈。
- POP_JUMP_IF_FALSE:弹出栈顶,如果是假则跳跃,如果为假,跳跃到指令码地址为34处,指令进入else代码块;如果不为假则进入到if代码块中,之后通过JUMP_FORWARD指令,跳跃出else代码块。
for循环控制语句
num = 0
for i in range(10):
num += i
1 0 LOAD_CONST 0 (0)
2 STORE_NAME 0 (num)
2 4 LOAD_NAME 1 (range)
6 LOAD_CONST 1 (10)
8 CALL_FUNCTION 1
10 GET_ITER
>> 12 FOR_ITER 12 (to 26)
14 STORE_NAME 2 (i)
3 16 LOAD_NAME 0 (num)
18 LOAD_NAME 2 (i)
20 INPLACE_ADD
22 STORE_NAME 0 (num)
24 JUMP_ABSOLUTE 12
>> 26 LOAD_CONST 2 (None)
28 RETURN_VALUE
None
- LOAD_NAME:按照f_locals,f_globals,f_builtin的搜索顺序搜索变量range,将搜索到的变量值压入栈中,此变量应当在内建变量中找到。
- LOAD_CONST:从consts元组中取出变量0并压入栈中,变量0的位置在元组下标0处。
- CALL_FUNCTION:调用函数,参数为1,即从栈中弹出一个元素,作为函数执行的参数,参数弹出后,再弹一个元素,作为可调用函数。该段代码调用了range函数,执行后的函数返回了一个可迭代对象,推入了栈中。
GET_ITER:
case TARGET(GET_ITER): { /* before: [obj]; after [getiter(obj)] */ PyObject *iterable = TOP(); PyObject *iter = PyObject_GetIter(iterable); Py_DECREF(iterable); SET_TOP(iter); if (iter == NULL) goto error; PREDICT(FOR_ITER); PREDICT(CALL_FUNCTION); DISPATCH(); }
- TOP():取出栈顶元素,栈顶元素是一个可迭代对象。
- PyObject_GetIter:该函数会拿到可迭代函数的迭代器。
- SET_TOP(iter):将迭代器放在栈顶, 覆盖原来的可迭代对象。
FOR_ITER:
case TARGET(FOR_ITER): { PREDICTED(FOR_ITER); /* before: [iter]; after: [iter, iter()] *or* [] */ PyObject *iter = TOP(); PyObject *next = (*Py_TYPE(iter)->tp_iternext)(iter); if (next != NULL) { PUSH(next); PREDICT(STORE_FAST); PREDICT(UNPACK_SEQUENCE); DISPATCH(); } if (_PyErr_Occurred(tstate)) { if (!_PyErr_ExceptionMatches(tstate, PyExc_StopIteration)) { goto error; } else if (tstate->c_tracefunc != NULL) { call_exc_trace(tstate->c_tracefunc, tstate->c_traceobjtstate, f, &trace_info); } _PyErr_Clear(tstate); } /* iterator ended normally */ STACK_SHRINK(1); Py_DECREF(iter); JUMPBY(oparg); DISPATCH(); }
- 将栈顶的迭代器拿到,调用迭代器的*Py_TYPE(iter)->tp_iternext函数获取下一个元素,如果拿到了元素,即指针next不为null,则将元素推入栈中。预测下一指令码,使用宏函数直接跳转,
#define DISPATCH() goto predispatch;
- 迭代完成后,next指针为null,将会执行JUMPBY(oparg)宏函数:
#define JUMPBY(x) (next_instr += (x))
,即指令码跳转;在此段代码中跳转12位,即六个指令码,即即将执行指令码地址26,LOAD_CONST指令码。
- 将栈顶的迭代器拿到,调用迭代器的*Py_TYPE(iter)->tp_iternext函数获取下一个元素,如果拿到了元素,即指针next不为null,则将元素推入栈中。预测下一指令码,使用宏函数直接跳转,
- STORE_NAME:将迭代器推入栈的值赋与变量i,存入f_locals中。
- LOAD_NAME:取变量num,压入栈。
- LOAD_NAME:取变量i,压入栈。
INPLACE_ADD:
case TARGET(INPLACE_ADD): { PyObject *right = POP(); PyObject *left = TOP(); PyObject *sum; if (PyUnicode_CheckExact(left) && PyUnicode_CheckExact(right)) { sum = unicode_concatenate(tstate, left, right, f, next_instr); /* unicode_concatenate consumed the ref to left */ } else { sum = PyNumber_InPlaceAdd(left, right); Py_DECREF(left); } Py_DECREF(right); SET_TOP(sum); if (sum == NULL) goto error; DISPATCH(); }
- 相加的结果存入栈顶
- STORE_NAME:将INPLACE_ADD的结果保存到变量num中。
JUMP_ABSOLUTE:执行函数
JUMPTO(oparg);
,绝对地址跳转。case TARGET(JUMP_ABSOLUTE): { PREDICTED(JUMP_ABSOLUTE); JUMPTO(oparg); CHECK_EVAL_BREAKER(); DISPATCH(); }
while 循环控制流
num = 0
while num < 10:
if num == 2:
continue
if num < 0:
break
num += 1
1 0 LOAD_CONST 0 (0)
2 STORE_NAME 0 (num)
2 >> 4 LOAD_NAME 0 (num)
6 LOAD_CONST 1 (10)
8 COMPARE_OP 0 (<)
10 POP_JUMP_IF_FALSE 42
3 12 LOAD_NAME 0 (num)
14 LOAD_CONST 2 (2)
16 COMPARE_OP 2 (==)
18 POP_JUMP_IF_FALSE 22
4 20 JUMP_ABSOLUTE 4
5 >> 22 LOAD_NAME 0 (num)
24 LOAD_CONST 0 (0)
26 COMPARE_OP 0 (<)
28 POP_JUMP_IF_FALSE 32
6 30 JUMP_ABSOLUTE 42
7 >> 32 LOAD_NAME 0 (num)
34 LOAD_CONST 3 (1)
36 INPLACE_ADD
38 STORE_NAME 0 (num)
40 JUMP_ABSOLUTE 4
>> 42 LOAD_CONST 4 (None)
44 RETURN_VALUE
None
- 所有指令均已分析过。
- 注意到while语句循环使用比较和跳转指令实现。
- 注意到break与continue语句使用JUMP_ABSOLUTE绝对跳转语句实现。
Comments | NOTHING