CPython 的解释器如何知道打印最后一个表达式的结果?
How does CPython's interpreter know to print the result of the last expression?
我一直在深入研究源代码,以弄清楚打印结果的位置。例如:
>>> x = 1
>>> x + 2
3
以上两条语句编译为:
1 0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (x)
6 LOAD_CONST 1 (None)
9 RETURN_VALUE
和
1 0 LOAD_NAME 0 (x)
3 LOAD_CONST 0 (2)
6 BINARY_ADD
7 RETURN_VALUE
第一条语句不打印任何内容,因为 None
是返回值。第二个returns相加的结果
CPython 的交互式循环调用 PyRun_InteractiveOneObjectEx()
for each input. This gets the AST and invokes run_mod()
to compile that AST to byte code and then evaluate the result in the virtual machine. The returned Python object that PyRun_InteractiveOneObjectEx()
gets is simply the top of the VM's stack.
到目前为止,所有这些都是我所期望的。但是然后返回值好像是thrown away! REPL 何时打印?
顺便说一句,我可以看到交互模式确实改变了分词器;它 invokes PyOS_Readline
带有 sys.ps1
提示符(默认为 ">>> "
)。我检查了 pythonrun.c
中的类似更改,但没有成功。
您正在展示通过在函数中包含代码生成的字节码的反汇编。这不是交互式代码的编译方式:它使用特殊的 'single' 模式(compile()
的第 3 个参数,如果您在 Python 代码中执行等效操作)。在这种模式下,丢弃每个表达式值的 POP_TOP
操作码变成了 PRINT_EXPR
。 x = 1
什么都不打印的原因是语句在堆栈上没有留下任何需要弹出的东西,所以这个转换不适用。
正确!为了后代,这里是对正在发生的事情的更深入的了解。
以上反汇编显示eval
模式的结果:
>>> list(compile('x + 2', '<stdin>', 'eval').co_code)
[101, 0, 0, 100, 0, 0, 23, 83]
可以通过以下方式查看操作码:
>>> import dis
>>> dis.opname[101]
'LOAD_NAME'
>>> dis.opname[100]
'LOAD_CONST'
>>> dis.opname[23]
'BINARY_ADD'
>>> dis.opname[83]
'RETURN_VALUE'
操作码后面的两个数字表示一个 16 位操作数,尽管这里只需要第一个字节。所以这对应于:
LOAD_NAME 0
LOAD_CONST 0
BINARY_ADD
RETURN_VALUE
这些操作数分别是编译代码对象的变量名和常量池的索引。
>>> c = compile('x + 2', '<stdin>', 'eval')
>>> c.co_names
('x',)
>>> c.co_consts
(2,)
到目前为止,这就是我们的问题。但实际上,执行 Python 代码会导致:
>>> list(compile('x + 2', '<stdin>', 'exec').co_code)
[101, 0, 0, 100, 0, 0, 23, 1, 100, 1, 0, 83]
>>> dis.opname[1]
'POP_TOP'
即,结果被丢弃并引入 None
作为 return 值。
在交互模式下,我们有:
>>> list(compile('x + 2', '<stdin>', 'single').co_code)
[101, 0, 0, 100, 0, 0, 23, 70, 100, 1, 0, 83]
>>> dis.opname[70]
'PRINT_EXPR'
打印结果(通过 sys.displayhook
)并且 None
成为实际的 return 值。
所以打印是由代码生成阶段引入的,而不是由 VM 引入的。
我一直在深入研究源代码,以弄清楚打印结果的位置。例如:
>>> x = 1
>>> x + 2
3
以上两条语句编译为:
1 0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (x)
6 LOAD_CONST 1 (None)
9 RETURN_VALUE
和
1 0 LOAD_NAME 0 (x)
3 LOAD_CONST 0 (2)
6 BINARY_ADD
7 RETURN_VALUE
第一条语句不打印任何内容,因为 None
是返回值。第二个returns相加的结果
CPython 的交互式循环调用 PyRun_InteractiveOneObjectEx()
for each input. This gets the AST and invokes run_mod()
to compile that AST to byte code and then evaluate the result in the virtual machine. The returned Python object that PyRun_InteractiveOneObjectEx()
gets is simply the top of the VM's stack.
到目前为止,所有这些都是我所期望的。但是然后返回值好像是thrown away! REPL 何时打印?
顺便说一句,我可以看到交互模式确实改变了分词器;它 invokes PyOS_Readline
带有 sys.ps1
提示符(默认为 ">>> "
)。我检查了 pythonrun.c
中的类似更改,但没有成功。
您正在展示通过在函数中包含代码生成的字节码的反汇编。这不是交互式代码的编译方式:它使用特殊的 'single' 模式(compile()
的第 3 个参数,如果您在 Python 代码中执行等效操作)。在这种模式下,丢弃每个表达式值的 POP_TOP
操作码变成了 PRINT_EXPR
。 x = 1
什么都不打印的原因是语句在堆栈上没有留下任何需要弹出的东西,所以这个转换不适用。
正确
以上反汇编显示eval
模式的结果:
>>> list(compile('x + 2', '<stdin>', 'eval').co_code)
[101, 0, 0, 100, 0, 0, 23, 83]
可以通过以下方式查看操作码:
>>> import dis
>>> dis.opname[101]
'LOAD_NAME'
>>> dis.opname[100]
'LOAD_CONST'
>>> dis.opname[23]
'BINARY_ADD'
>>> dis.opname[83]
'RETURN_VALUE'
操作码后面的两个数字表示一个 16 位操作数,尽管这里只需要第一个字节。所以这对应于:
LOAD_NAME 0
LOAD_CONST 0
BINARY_ADD
RETURN_VALUE
这些操作数分别是编译代码对象的变量名和常量池的索引。
>>> c = compile('x + 2', '<stdin>', 'eval')
>>> c.co_names
('x',)
>>> c.co_consts
(2,)
到目前为止,这就是我们的问题。但实际上,执行 Python 代码会导致:
>>> list(compile('x + 2', '<stdin>', 'exec').co_code)
[101, 0, 0, 100, 0, 0, 23, 1, 100, 1, 0, 83]
>>> dis.opname[1]
'POP_TOP'
即,结果被丢弃并引入 None
作为 return 值。
在交互模式下,我们有:
>>> list(compile('x + 2', '<stdin>', 'single').co_code)
[101, 0, 0, 100, 0, 0, 23, 70, 100, 1, 0, 83]
>>> dis.opname[70]
'PRINT_EXPR'
打印结果(通过 sys.displayhook
)并且 None
成为实际的 return 值。
所以打印是由代码生成阶段引入的,而不是由 VM 引入的。