【问题标题】:How does CPython's interpreter know to print the result of the last expression?CPython 的解释器如何知道打印最后一个表达式的结果?
【发布时间】:2018-05-16 15:02:00
【问题描述】:

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

>>> 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_Readlinesys.ps1 提示符(默认为">>> ")。我检查了pythonrun.c 中的类似变化,但没有运气。

【问题讨论】:

    标签: python cpython python-internals


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      正确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 引入的。

      【讨论】:

        猜你喜欢
        • 2019-08-09
        • 1970-01-01
        • 1970-01-01
        • 2020-12-28
        • 1970-01-01
        • 2023-03-23
        • 2020-03-25
        • 1970-01-01
        • 2010-12-04
        相关资源
        最近更新 更多