Python编译/解释过程

我试图更清楚地理解python编译器/解释器过程。 不幸的是,我没有在口译员课上讲课,也没有多读些关于他们的内容。

基本上,我现在明白的是,.py文件中的Python代码首先被编译成python字节码(我认为它是偶尔看到的.pyc文件?)。 接下来,字节码被编译成机器代码,这是处理器实际理解的语言。 很多,我读过这个线程为什么Python在解释之前将源代码编译成字节码?

记住我对编译器/解释器的知识几乎是不存在的,是否有人能够完整地解释整个过程? 或者,如果这是不可能的,也许给我一些资源来快速浏览编译器/解释器?

谢谢


字节码实际上没有解释为机器码,除非你使用了一些特殊的实现,如pypy。

除此之外,你的描述是正确的。 字节码被加载到Python运行时并由虚拟机解释,虚拟机是一段读取字节码中每条指令的代码,并执行指定的任何操作。 您可以使用dis模块查看该字节码,如下所示:

>>> def fib(n): return n if n < 2 else fib(n - 2) + fib(n - 1)
... 
>>> fib(10)
55
>>> import dis
>>> dis.dis(fib)
  1           0 LOAD_FAST                0 (n)
              3 LOAD_CONST               1 (2)
              6 COMPARE_OP               0 (<)
              9 JUMP_IF_FALSE            5 (to 17)
             12 POP_TOP             
             13 LOAD_FAST                0 (n)
             16 RETURN_VALUE        
        >>   17 POP_TOP             
             18 LOAD_GLOBAL              0 (fib)
             21 LOAD_FAST                0 (n)
             24 LOAD_CONST               1 (2)
             27 BINARY_SUBTRACT     
             28 CALL_FUNCTION            1
             31 LOAD_GLOBAL              0 (fib)
             34 LOAD_FAST                0 (n)
             37 LOAD_CONST               2 (1)
             40 BINARY_SUBTRACT     
             41 CALL_FUNCTION            1
             44 BINARY_ADD          
             45 RETURN_VALUE        
>>> 

详细的解释

理解上述代码永远不会被CPU执行是非常重要的。 也没有被转换成(至少,不是Python的官方C实现)。 CPU执行虚拟机代码,执行由字节码指令指示的工作。 当解释者想要执行fib函数时,它会一次读取一条指令,并执行他们要求的操作。 它查看第一条指令LOAD_FAST 0 ,从而从参数被保存的任何地方抓取参数0( n传递给fib )并将其推送到解释器的堆栈上(Python的解释器是堆栈机器)。 在读取下一条指令LOAD_CONST 1 ,它从函数拥有的常量集合中获取常数1,在这种情况下恰好是数字2,并将其推入堆栈。 你可以看到这些常量:

>>> fib.func_code.co_consts
(None, 2, 1)

下一条指令COMPARE_OP 0告诉解释器弹出两个最上面的堆栈元素,并在它们之间执行不等式比较,将布尔结果推回堆栈。 第四条指令根据布尔值确定是跳转五条指令还是继续下一条指令。 所有这些措辞解释了if n < 2的条件表达式在fib 。 这将是一个非常具有启发性的练习,以梳理其余fib字节码的含义和行为。 唯一一个我不确定的是POP_TOP ; 我猜JUMP_IF_FALSE被定义为将其布尔参数留在堆栈上而不是弹出它,所以它必须被明确地弹出。

更有意义的是检查fib的原始字节码:

>>> code = fib.func_code.co_code
>>> code
'|x00x00dx01x00jx00x00ox05x00x01|x00x00Sx01tx00x00|x00x00dx01x00x18x83x01x00tx00x00|x00x00dx02x00x18x83x01x00x17S'
>>> import opcode
>>> op = code[0]
>>> op
'|'
>>> op = ord(op)
>>> op
124
>>> opcode.opname[op]
'LOAD_FAST'
>>> 

因此你可以看到字节码的第一个字节是LOAD_FAST指令。 下一对字节'x00x00' (16位中的数字0)是LOAD_FAST的参数,并告诉字节码解释器将参数0加载到堆栈上。


为了完成Marcelo Cantos的出色答案,这里只是一个小小的逐列总结来解释反汇编字节码的输出。

例如,给定这个功能:

def f(num):
    if num == 42:
        return True
    return False

这可能会被反汇编成(Python 3.6):

(1)|(2)|(3)|(4)|          (5)         |(6)|  (7)
---|---|---|---|----------------------|---|-------
  2|   |   |  0|LOAD_FAST             |  0|(num)
   |-->|   |  2|LOAD_CONST            |  1|(42)
   |   |   |  4|COMPARE_OP            |  2|(==)
   |   |   |  6|POP_JUMP_IF_FALSE     | 12|
   |   |   |   |                      |   |
  3|   |   |  8|LOAD_CONST            |  2|(True)
   |   |   | 10|RETURN_VALUE          |   |
   |   |   |   |                      |   |
  4|   |>> | 12|LOAD_CONST            |  3|(False)
   |   |   | 14|RETURN_VALUE          |   |

每一列都有一个特定的目的:

  • 源代码中的相应行号
  • 可选地,指示当前执行的指令 (例如,当字节码来自帧对象时)
  • 一个标签,表示从此前的一条指令可能的JUMP
  • 与字节索引相对应的字节码中的地址 (它们是2的倍数,因为Python 3.6每条指令使用2个字节,而在以前的版本中它可能会有所不同)
  • 指令名称(也称为opname ),每个都在dis模块中进行简要说明,它们的实现可以在ceval.c找到(CPython的核心循环)
  • 其通过的Python内部使用的参数 (如果有的话)的指令的取回一些常数或变量,管理堆栈,跳转到特定指令等
  • 对教学论证的人性化解释
  • 链接地址: http://www.djcxy.com/p/52557.html

    上一篇: Python Compilation/Interpretation Process

    下一篇: Compiled vs. Interpreted Languages