CPython的解释器如何知道打印最后一个表达式的结果?

时间:2018-05-16 15:02:00

标签: python cpython python-internals

我一直在挖掘源代码,以确定打印结果的时间点。例如:

>>> 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是返回值。第二个返回添加的结果。

CPython的交互式循环为每个输入调用PyRun_InteractiveOneObjectEx()。这个gets the AST并在虚拟机中调用run_mod()compile that AST to byte code然后调用evaluate the resultPyRun_InteractiveOneObjectEx()获得的返回Python对象只是top of the VM's stack

到目前为止,所有这些都是我所期待的。但是返回的值似乎是thrown away!什么时候由REPL打印?

顺便说一句,我可以看到交互模式确实改变了标记器;它是invokes PyOS_Readline sys.ps1提示符(默认为">>> ")。我检查了pythonrun.c中的类似更改,但没有运气。

2 个答案:

答案 0 :(得分:5)

您正在显示通过在函数中包含代码而生成的字节码的反汇编。这不是如何编译交互式代码的:它使用了一个特殊的单个' mode(第3个参数为compile(),如果您在Python代码中执行等效操作)。在此模式下,丢弃每个表达式值的POP_TOP操作码将转换为PRINT_EXPRx = 1没有打印任何内容的原因是语句不会在需要弹出的堆栈上留下任何内容,因此这种转换并不适用。

答案 1 :(得分:0)

纠正answer from @jasonharper!对于子孙后代,我们可以更深入地了解正在发生的事情。

上面的反汇编显示了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作为返回值。

在互动模式下,我们有:

>>> 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成为实际返回值。

因此,打印是由代码生成阶段引入的,而不是由VM引入的。