美文网首页
大师兄的Python源码学习笔记(十四): 虚拟机中的控制流(一

大师兄的Python源码学习笔记(十四): 虚拟机中的控制流(一

作者: superkmi | 来源:发表于2021-05-02 11:04 被阅读0次

    大师兄的Python源码学习笔记(十三): Python虚拟机中的一般表达式(二)
    大师兄的Python源码学习笔记(十五): 虚拟机中的控制流(二)

    一、虚拟机中的if控制流

    • 在所有编程语言中,if控制流是最简单最常用的流控制语句,如果我们编写一段简单的代码:
    demo.py
    
    a=10
    if a>1:
        ...
    elif a <= -5:
        ...
    elif a != 10:
        ...
    elif a ==10:
        ...
    else:
        ...
    
    • 下面是demo.py生成的字节码指令序列:
      1           0 LOAD_CONST               0 (10)
                  2 STORE_NAME               0 (a)
    
      2           4 LOAD_NAME                0 (a)
                  6 LOAD_CONST               1 (1)
                  8 COMPARE_OP               4 (>)
                 10 POP_JUMP_IF_FALSE       14
    
      3          12 JUMP_FORWARD            30 (to 44)
    
      4     >>   14 LOAD_NAME                0 (a)
                 16 LOAD_CONST               2 (-5)
                 18 COMPARE_OP               1 (<=)
                 20 POP_JUMP_IF_FALSE       24
    
      5          22 JUMP_FORWARD            20 (to 44)
    
      6     >>   24 LOAD_NAME                0 (a)
                 26 LOAD_CONST               0 (10)
                 28 COMPARE_OP               3 (!=)
                 30 POP_JUMP_IF_FALSE       34
    
      7          32 JUMP_FORWARD            10 (to 44)
    
      8     >>   34 LOAD_NAME                0 (a)
                 36 LOAD_CONST               0 (10)
                 38 COMPARE_OP               2 (==)
                 40 POP_JUMP_IF_FALSE       44
    
      9          42 JUMP_FORWARD             0 (to 44)
    
     11     >>   44 LOAD_CONST               3 (None)
                 46 RETURN_VALUE
    
    • 根据以往的学习,可以了解到LOAD_CONSTSTORE_NAME是在PyFrameObject对象local名字空间添加a常量,并关联一个PyIntObject对象10。
      1           0 LOAD_CONST               0 (10)
                  2 STORE_NAME               0 (a)
    
    1.1 比较操作
    • Python中的if控制语句根据if语句处的判断结果,决定程序的流程走向。
    • 所谓判断,就是将两个对象进行比较,判断的结果,也就是比较的结果。
    if a>1:
    
    • 这段判断语句对应的字节码指令序列如下:
      2           4 LOAD_NAME                0 (a)
                  6 LOAD_CONST               1 (1)
                  8 COMPARE_OP               4 (>)
                 10 POP_JUMP_IF_FALSE       14
    
    • LOAD_NAME从local名字空间获得变量名a所对应的的变量值对象,这里我们知道是10。
    • LOAD_CONST从常量表consts中读取参与该分支判断操作的常量对象,这里是1。
    • COMPARE_OP对前面两条指令获得的对象进行比较操作。
    • POP_JUMP_IF_FALSE根据COMPARE_OP指令的运行结果进行字节码指令的跳跃。
    1.2 比较过程
    • 从上面的结构可以看出,COMPARE_OP传入的参数是区分不同分支判断的关键。
    ceval.c
    
            TARGET(COMPARE_OP) {
                PyObject *right = POP();
                PyObject *left = TOP();
                PyObject *res = cmp_outcome(oparg, left, right);
                Py_DECREF(left);
                Py_DECREF(right);
                SET_TOP(res);
                if (res == NULL)
                    goto error;
                PREDICT(POP_JUMP_IF_FALSE);
                PREDICT(POP_JUMP_IF_TRUE);
                DISPATCH();
            }
    
    • 这段代码从栈中获取了左TOP()POP()两个值,并通过cmp_outcome函数获得进行比较结果。
    ceval.c
    
    static PyObject *
    cmp_outcome(int op, PyObject *v, PyObject *w)
    {
        int res = 0;
        switch (op) {
        case PyCmp_IS:
            res = (v == w);
            break;
        case PyCmp_IS_NOT:
            res = (v != w);
            break;
        case PyCmp_IN:
            res = PySequence_Contains(w, v);
            if (res < 0)
                return NULL;
            break;
        case PyCmp_NOT_IN:
            res = PySequence_Contains(w, v);
            if (res < 0)
                return NULL;
            res = !res;
            break;
        case PyCmp_EXC_MATCH:
            if (PyTuple_Check(w)) {
                Py_ssize_t i, length;
                length = PyTuple_Size(w);
                for (i = 0; i < length; i += 1) {
                    PyObject *exc = PyTuple_GET_ITEM(w, i);
                    if (!PyExceptionClass_Check(exc)) {
                        PyErr_SetString(PyExc_TypeError,
                                        CANNOT_CATCH_MSG);
                        return NULL;
                    }
                }
            }
            else {
                if (!PyExceptionClass_Check(w)) {
                    PyErr_SetString(PyExc_TypeError,
                                    CANNOT_CATCH_MSG);
                    return NULL;
                }
            }
            res = PyErr_GivenExceptionMatches(v, w);
            break;
        default:
            return PyObject_RichCompare(v, w, op);
        }
        v = res ? Py_True : Py_False;
        Py_INCREF(v);
        return v;
    }
    
    • 从上面的代码中,我们可以看到COMPARE_OP指令不仅管辖着两个对象之间的比较操作,还覆盖了对象与集合之间关系的判断操作,比如在PyCmp_IN中,使用了PySequence_Contains函数进一步判断:
    case PyCmp_IN:
            res = PySequence_Contains(w, v);
            if (res < 0)
                return NULL;
            break;
    
    Objects\abstract.c
    
    /* Return -1 if error; 1 if ob in seq; 0 if ob not in seq.
     * Use sq_contains if possible, else defer to _PySequence_IterSearch().
     */
    int
    PySequence_Contains(PyObject *seq, PyObject *ob)
    {
        Py_ssize_t result;
        PySequenceMethods *sqm = seq->ob_type->tp_as_sequence;
        if (sqm != NULL && sqm->sq_contains != NULL)
            return (*sqm->sq_contains)(seq, ob);
        result = _PySequence_IterSearch(seq, ob, PY_ITERSEARCH_CONTAINS);
        return Py_SAFE_DOWNCAST(result, Py_ssize_t, int);
    }
    
    • 而对于两个对象之间的比较,则使用了PyObject_RichCompare函数:
    Objects\object.c
    
    PyObject *
    PyObject_RichCompare(PyObject *v, PyObject *w, int op)
    {
        PyObject *res;
    
        assert(Py_LT <= op && op <= Py_GE);
        if (v == NULL || w == NULL) {
            if (!PyErr_Occurred())
                PyErr_BadInternalCall();
            return NULL;
        }
        if (Py_EnterRecursiveCall(" in comparison"))
            return NULL;
        res = do_richcompare(v, w, op);
        Py_LeaveRecursiveCall();
        return res;
    }
    
    • PyObject_RichCompare函数实际调用了do_richcompare函数实现了对象的比对。
    Objects\object.c
    
    static PyObject *
    do_richcompare(PyObject *v, PyObject *w, int op)
    {
        richcmpfunc f;
        PyObject *res;
        int checked_reverse_op = 0;
    
        if (v->ob_type != w->ob_type &&
            PyType_IsSubtype(w->ob_type, v->ob_type) &&
            (f = w->ob_type->tp_richcompare) != NULL) {
            checked_reverse_op = 1;
            res = (*f)(w, v, _Py_SwappedOp[op]);
            if (res != Py_NotImplemented)
                return res;
            Py_DECREF(res);
        }
        if ((f = v->ob_type->tp_richcompare) != NULL) {
            res = (*f)(v, w, op);
            if (res != Py_NotImplemented)
                return res;
            Py_DECREF(res);
        }
        if (!checked_reverse_op && (f = w->ob_type->tp_richcompare) != NULL) {
            res = (*f)(w, v, _Py_SwappedOp[op]);
            if (res != Py_NotImplemented)
                return res;
            Py_DECREF(res);
        }
        /* If neither object implements it, provide a sensible default
           for == and !=, but raise an exception for ordering. */
        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(PyExc_TypeError,
                         "'%s' not supported between instances of '%.100s' and '%.100s'",
                         opstrings[op],
                         v->ob_type->tp_name,
                         w->ob_type->tp_name);
            return NULL;
        }
        Py_INCREF(res);
        return res;
    }
    
    1.3 比较的结果
    • 通过复杂的比对过程,Python会获得一个bool值作为结果。
    v = res ? Py_True : Py_False;
    
    • Py_TruePy_False是两个PyObject对象
    Include\boolobject.h
    
    /* Don't use these directly */
    PyAPI_DATA(struct _longobject) _Py_FalseStruct, _Py_TrueStruct;
    
    /* Use these macros */
    #define Py_False ((PyObject *) &_Py_FalseStruct)
    #define Py_True ((PyObject *) &_Py_TrueStruct)
    
    • Py_TruePy_False其实底层和c语言一样就是1和0。
    Objects\boolobject.c
    
    PyTypeObject PyBool_Type = {
        PyVarObject_HEAD_INIT(&PyType_Type, 0)
        "bool",
        sizeof(struct _longobject),
        0,
        0,                                          /* tp_dealloc */
        0,                                          /* tp_print */
        0,                                          /* tp_getattr */
        0,                                          /* tp_setattr */
        0,                                          /* tp_reserved */
        bool_repr,                                  /* tp_repr */
        &bool_as_number,                            /* tp_as_number */
        0,                                          /* tp_as_sequence */
        0,                                          /* tp_as_mapping */
        0,                                          /* tp_hash */
        0,                                          /* tp_call */
        bool_repr,                                  /* tp_str */
        0,                                          /* tp_getattro */
        0,                                          /* tp_setattro */
        0,                                          /* tp_as_buffer */
        Py_TPFLAGS_DEFAULT,                         /* tp_flags */
        bool_doc,                                   /* tp_doc */
        0,                                          /* tp_traverse */
        0,                                          /* tp_clear */
        0,                                          /* tp_richcompare */
        0,                                          /* tp_weaklistoffset */
        0,                                          /* tp_iter */
        0,                                          /* tp_iternext */
        0,                                          /* tp_methods */
        0,                                          /* tp_members */
        0,                                          /* tp_getset */
        &PyLong_Type,                               /* tp_base */
        0,                                          /* tp_dict */
        0,                                          /* tp_descr_get */
        0,                                          /* tp_descr_set */
        0,                                          /* tp_dictoffset */
        0,                                          /* tp_init */
        0,                                          /* tp_alloc */
        bool_new,                                   /* tp_new */
    };
    
    /* The objects representing bool values False and True */
    
    struct _longobject _Py_FalseStruct = {
        PyVarObject_HEAD_INIT(&PyBool_Type, 0)
        { 0 }
    };
    
    struct _longobject _Py_TrueStruct = {
        PyVarObject_HEAD_INIT(&PyBool_Type, 1)
        { 1 }
    };
    
    1.4 指令的跳跃
    • 如果判断结果为False,虚拟机会执行指令跳跃操作
    10 POP_JUMP_IF_FALSE       14
    
    • 对应的代码如下:
    ceval.c
    
            PREDICTED(POP_JUMP_IF_FALSE);
            TARGET(POP_JUMP_IF_FALSE) {
                PyObject *cond = POP();
                int err;
                if (cond == Py_True) {
                    Py_DECREF(cond);
                    FAST_DISPATCH();
                }
                if (cond == Py_False) {
                    Py_DECREF(cond);
                    JUMPTO(oparg);
                    FAST_DISPATCH();
                }
                err = PyObject_IsTrue(cond);
                Py_DECREF(cond);
                if (err > 0)
                    ;
                else if (err == 0)
                    JUMPTO(oparg);
                else
                    goto error;
                DISPATCH();
            }
    
    • 这里实际是通过宏JUMPTO跳到下一段字节码的位置:
    ceval.c
    
    #define JUMPTO(x)       (next_instr = first_instr + (x) / sizeof(_Py_CODEUNIT))
    
    • 如果最终结果判断为TRUE,则通过JUMP_FORWARD指令跳到if控制结构的末尾。
    ceval.c
    
            TARGET(JUMP_FORWARD) {
                JUMPBY(oparg);
                FAST_DISPATCH();
            }
    
    • 这里实际通过JUMPBY宏实现跳转到if控制结构末尾:
    ceval.c
    
    #define JUMPBY(x)       (next_instr += (x) / sizeof(_Py_CODEUNIT))
    

    相关文章

      网友评论

          本文标题:大师兄的Python源码学习笔记(十四): 虚拟机中的控制流(一

          本文链接:https://www.haomeiwen.com/subject/oxrorltx.html