Python 控制流程


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指令码。
  • 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绝对跳转语句实现。

声明:Hello World|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Python 控制流程


我的朋友,理论是灰色的,而生活之树是常青的!