如何在Python中找到for-control流构造的实现

时间:2019-02-19 07:07:22

标签: python cpython

已经在SO中搜索了相同的内容,也看到了CPython的github存储库;但无济于事。似乎任何控制流构造的源代码实现都是不可见的,但不清楚为什么?

特别需要获取CPython中“用于-控制流构造”的源代码。

在不知情的情况下,我所能做的就是在小代码上使用dis模块的dis(),导致产生FOR_ITER操作码,这对我来说是无法理解的。
该操作码也没有使我理解嵌套的for循环构造的工作原理,这也是我想研究其在源代码中的实现的原因。

>import dis
 def foo():
 for i in range(3):
     for j in range(2):
         print(i,j)
 dis.dis(foo)

 3           0 SETUP_LOOP              44 (to 46)
             2 LOAD_GLOBAL              0 (range)
             4 LOAD_CONST               1 (3)
             6 CALL_FUNCTION            1
             8 GET_ITER
       >>   10 FOR_ITER                32 (to 44)
            12 STORE_FAST               0 (i)

 4          14 SETUP_LOOP              26 (to 42)
            16 LOAD_GLOBAL              0 (range)
            18 LOAD_CONST               2 (2)
            20 CALL_FUNCTION            1
            22 GET_ITER
       >>   24 FOR_ITER                14 (to 40)
            26 STORE_FAST               1 (j)

 5          28 LOAD_GLOBAL              1 (print)
            30 LOAD_FAST                0 (i)
            32 LOAD_FAST                1 (j)
            34 CALL_FUNCTION            2
            36 POP_TOP
            38 JUMP_ABSOLUTE           24
       >>   40 POP_BLOCK
       >>   42 JUMP_ABSOLUTE           10
       >>   44 POP_BLOCK
       >>   46 LOAD_CONST               0 (None)
            48 RETURN_VALUE

2 个答案:

答案 0 :(得分:1)

考虑当前CPython代码库(3.8.5)上的主题:

您可以在反汇编中看到,每个FOR_ITER之前都有一个GET_ITER

GET_ITER源代码(检查编号注释):

    case TARGET(GET_ITER): {
        /* before: [obj]; after [getiter(obj)] */
        PyObject *iterable = TOP(); // 1.
        PyObject *iter = PyObject_GetIter(iterable); // 2.
        Py_DECREF(iterable); // 3.
        SET_TOP(iter); // 4.
        if (iter == NULL)
            goto error;
        PREDICT(FOR_ITER);
        PREDICT(CALL_FUNCTION);
        DISPATCH();
    }

GET_ITER实际上将PyObject_GetIter循环遍历的对象iterable传递给for

代码:

  1. 使iterable指向stack of python objects的顶部;
  2. 使iter指向由PyObject_GetIter调用返回的迭代器;
  3. 减少对iterable的引用计数;
  4. 迭代器iter现在位于堆栈顶部。

PyObject_GetIter检查可迭代对象是否为迭代器(即消耗可迭代对象的对象),如果是则返回它。如果不是,则检查它是否是序列。如果是序列,则将其转换为迭代器。该迭代器是返回值。


FOR_ITER代码:

    case TARGET(FOR_ITER): {
        PREDICTED(FOR_ITER);
        /* before: [iter]; after: [iter, iter()] *or* [] */
        PyObject *iter = TOP(); // 1.
        PyObject *next = (*iter->ob_type->tp_iternext)(iter); // 2.
        if (next != NULL) {
            PUSH(next); // 3.
            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_traceobj, tstate, f);
            }
            _PyErr_Clear(tstate);
        }
        /* iterator ended normally */
        STACK_SHRINK(1);
        Py_DECREF(iter);
        JUMPBY(oparg);
        PREDICT(POP_BLOCK);
        DISPATCH();
    }

感兴趣的部分:

  1. 从堆栈顶部获取迭代器;
  2. 获取next (i.e. tp_iternext)方法调用的结果;
  3. 如果不是NULL,则将结果压入堆栈。

您应该问的一件事:这仅涵盖循环的单个迭代。使迭代器遍历所有项目的代码在哪里?

JUMP_ABSOLUTE操作码使迭代器再次运行,这次在下一个元素上运行。您可以在原始清单中看到,每个JUMP_ABSOLUTE都用相应的FOR_ITER操作码的行号调用,从而使迭代成为可能。

This answer也是该主题的很好参考。

答案 1 :(得分:0)

该实现已添加到this commit中;这是关于FOR_ITER的部分:

        case FOR_ITER:
            /* before: [iter]; after: [iter, iter()] *or* [] */
            v = TOP();
            x = PyObject_CallObject(v, NULL);
            if (x == NULL) {
                if (PyErr_ExceptionMatches(
                    PyExc_StopIteration))
                {
                    PyErr_Clear();
                    x = v = POP();
                    Py_DECREF(v);
                    JUMPBY(oparg);
                    continue;
                }
                break;
            }
            PUSH(x);
            continue;

忽略引用,for x in y:循环等效于以下Python代码:

# GET_ITER
y_iter = iter(y)

# FOR_ITER
while True:
    try:
        x = next(y_iter)
    except StopIteration:
        break

    # body of for loop
    pass