我正在试验Python操作码,很惊讶看到下面的dis.dis
输出。给出以下两行:
[i for i in range(10)]
print("OK")
如您所见,这两行导致3个块。为什么第一个和第三个块属于第一行?是否也按此顺序执行?我希望从C
或C++
编译器获得这样的乱序指令,但我不理解第三个代码块:
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函数及其参数,然后将其应用-我们现在在堆栈顶部有range(10)
。最后,将范围删除,并用其迭代器替换。
最后,Python调用helper函数:
14 CALL_FUNCTION 1
[请记住,我们的堆栈之前已经填充了辅助函数<listcomp>
和范围迭代器。其他所有东西都从堆栈中消失了。因此,此处Python以范围迭代器作为参数调用了辅助函数。帮助程序返回列表理解产生的列表。
>>
是什么?它们是字节码执行中可能的跳转/分支的可视化。
例如,可以完成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
—标有>>
的返回指令。由于循环必须对每个项目重复,因此它的最后一条指令12是跳转到4处的循环指令-也标记为>>
。