两行 Python 代码导致 3 个执行块
Two lines of Python code result in 3 execution blocks
我正在试验 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(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 处的循环指令 — 也标记为 >>
.
我正在试验 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(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 处的循环指令 — 也标记为 >>
.