两行 Python 代码导致 3 个执行块

Two lines of Python code result in 3 execution blocks

我正在试验 Python 操作码,我很惊讶地看到下面 dis.dis 的输出。给定以下两行:

[i for i in range(10)]
print("OK")

如您所见,这两行产生了 3 个块。为什么第一和第三块属于第一行?这也是按照这个顺序执行的吗?我希望来自 CC++ 编译器的此类乱序指令,但我不理解第三个块:

1           0 LOAD_CONST               0 (<code object <listcomp> at 0x109664540, file "example.py", line 1>)
            2 LOAD_CONST               1 ('<listcomp>')
            4 MAKE_FUNCTION            0
            6 LOAD_NAME                0 (range)
            8 LOAD_CONST               2 (10)
            10 CALL_FUNCTION            1
            12 GET_ITER
            14 CALL_FUNCTION            1
            16 POP_TOP

2          18 LOAD_NAME                1 (print)
            20 LOAD_CONST               3 ('OK')
            22 CALL_FUNCTION            1
            24 POP_TOP
            26 LOAD_CONST               4 (None)
            28 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x109664540, file "test", line 1>:
1           0 BUILD_LIST               0
            2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
            6 STORE_FAST               1 (i)
            8 LOAD_FAST                1 (i)
            10 LIST_APPEND              2
            12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE

P.S。 >> 是为了什么? 文档将它们描述为 a labelled instruction, indicated with >>, 但我不能在上面的示例中应用此评论。

<listcomp> 是解释器在对列表理解进行脱糖时创建的一个单独对象。它用于隔离理解命名空间(循环变量 i)并构建列表。它先被创建,然后在一些准备之后被调用,最后被丢弃。


您可以在开始时看到正在创建的助手:

1           0 LOAD_CONST               0 (<code object <listcomp> at 0x109664540, file "example.py", line 1>)
            2 LOAD_CONST               1 ('<listcomp>')
            4 MAKE_FUNCTION            0

由于 CPython 是基于堆栈的 VM,前面的指令可以将后面指令的参数放在堆栈上。 在这里您可以看到正在加载的代码对象(反汇编中的第三个块),正在加载的名称,最后是从刚刚添加到堆栈的内容创建的函数。请注意,您实际上可以看到此功能,例如如果理解中有错误,它会显示在回溯中。

接下来,Python 在范围内创建一个迭代器:

        6 LOAD_NAME                0 (range)
        8 LOAD_CONST               2 (10)
        10 CALL_FUNCTION            1
        12 GET_ITER

这会加载范围函数及其参数,然后应用它——我们现在在堆栈顶部有 range(10)。最后,范围被删除并用它的迭代器替换它。

最后,Python调用辅助函数:

        14 CALL_FUNCTION            1

请记住,我们的堆栈之前填充了辅助函数 <listcomp> 和范围迭代器。其他一切都从堆栈中消失了。所以这里 Python 以范围迭代器作为参数调用辅助函数。助手 return 是列表推导产生的列表。


  • 为了什么 >>?

它们是字节码执行中可能 jumps/branches 的可视化。

例如,for 循环可以完成(跳到循环的末尾)或有另一个项目(跳到循环的开始)。您可以在反汇编中看到这一点:

  >>    4 FOR_ITER                 8 (to 14)
        6 STORE_FAST               1 (i)
        8 LOAD_FAST                1 (i)
        10 LIST_APPEND              2
        12 JUMP_ABSOLUTE            4
   >>   14 RETURN_VALUE

指令4是一个循环的迭代(即在列表推导中)。它的参数(右边)是块大小,即 8。这意味着完成后,它会跳转 to 14 — 标记为 >> 的 return 指令。由于循环必须对每个项目重复,因此其最后一条指令 12 是跳转到循环指令 4 处的循环指令 — 也标记为 >>.