Python编译/解释过程

Nic*_*den 41 python compiler-construction interpreter

我试图更清楚地理解python编译器/解释器进程.不幸的是,我没有参加口译课,也没有读过很多关于它们的内容.

基本上,我现在理解的是.py文件中的Python代码首先编译成python字节码(我假设偶尔会看到.pyc文件?).接下来,字节码被编译成机器代码,这是处理器实际理解的语言.差不多,我已经读过这个帖子为什么python在解释前将源代码编译成字节码?

有人能给我一个很好的解释整个过程,记住我对编译器/解释器的了解几乎不存在吗?或者,如果这不可能,也许给我一些资源,快速概述编译器/解释器?

谢谢

Mar*_*tos 53

字节码实际上并不解释为机器代码,除非你使用一些奇特的实现,如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        
>>> 
Run Code Online (Sandbox Code Playgroud)

详细解释

了解上述代码永远不会被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)
Run Code Online (Sandbox Code Playgroud)

下一条指令COMPARE_OP 0告诉解释器弹出两个最顶层的堆栈元素并在它们之间执行不等式比较,将布尔结果推回堆栈.第四条指令根据布尔值确定是跳过五条指令还是继续执行下一条指令.所有这些措辞都解释if n < 2了条件表达式的一部分fib.对于您来说,梳理其余fib字节码的含义和行为将是一项非常有益的练习.唯一一个,我不确定是POP_TOP; 我猜测JUMP_IF_FALSE定义是将其布尔参数留在堆栈上而不是弹出它,因此必须明确地弹出它.

因此更有启发性的是检查原始字节码fib:

>>> code = fib.func_code.co_code
>>> code
'|\x00\x00d\x01\x00j\x00\x00o\x05\x00\x01|\x00\x00S\x01t\x00\x00|\x00\x00d\x01\x00\x18\x83\x01\x00t\x00\x00|\x00\x00d\x02\x00\x18\x83\x01\x00\x17S'
>>> import opcode
>>> op = code[0]
>>> op
'|'
>>> op = ord(op)
>>> op
124
>>> opcode.opname[op]
'LOAD_FAST'
>>> 
Run Code Online (Sandbox Code Playgroud)

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

  • 为了更清楚一点你的上一个问题:没有用于"与显卡通信"的Python操作码.有一个"在此模块中调用此函数"的操作码,如果模块是图形编程扩展模块,则解释器将调用库的入口点以获取所请求的函数,并向其传递一些参数.C库(假设它是C)梳理出参数,将它们从Python对象转换为C值和结构,并将调用转发到真正的图形库,然后在屏幕上或其他任何地方插入彩色三角形. (6认同)
  • 解释器/ VM在C中.它(稍微过分简化)一个循环,它使用当前字节来选择巨大的switch语句中的许多情况之一.在交换机中间的某个地方,有一个`case LOAD_FAST:`后跟代码读取接下来的两个字节,在一些"parameters"集合中查找指定的参数,并将其推送到堆栈对象.为了与外界进行交互,Python允许调用扩展模块,扩展模块的行为类似于Python代码和对象,但它们实际上是编译代码,因此可以直接代表您的脚本与图形卡等进行通信. (3认同)
  • 将 Python 字节码视为烹饪食谱,将 C 代码视为阅读和遵循食谱以烹饪食物的烹饪机器人可能会有所帮助。机器人本身内部有代码,很可能是 C 代码,而食谱只是通过机器人的眼睛读入的数据,以便了解如何执行特定的烹饪程序。在一个层面上,配方就是代码——一组要遵循的指令。在另一个层面上,它只是提供给机器人大脑的数据。第 (3认同)
  • @Moondra CPython从不将字节码转换为切换案例.我链接的C代码是编译成机器代码的代码.该机器代码是C代码的CPU就绪表示.您应该将C代码和机器代码视为完全相同的不同表示.C是人类可读的形式,而机器代码是机器可读的形式.要理解的一个关键点是C程序(以其编译的机器代码形式)是CPU看到的唯一代码. (2认同)
  • ……相比之下,Python 字节码被 CPU 视为只是数据。C 代码_解释_该数据作为要执行的代码,因此得名_解释器_。 (2认同)

Del*_*gan 5

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

例如,给定这个函数:

def f(num):
    if num == 42:
        return True
    return False
Run Code Online (Sandbox Code Playgroud)

这可以分解为(Python 3.6):

def f(num):
    if num == 42:
        return True
    return False
Run Code Online (Sandbox Code Playgroud)

每列都有特定的用途:

  1. 源码中对应的行号
  2. 可选地指示当前执行的指令(例如,当字节码来自框架对象时
  3. 一个标签,表示JUMP从较早的指令到这个指令的可能
  4. 地址对应于字节索引在字节码(这些都是2因为Python 3.6使用2个字节用于每个指令的倍数,而它可以在先前的版本而变化)
  5. 指令名称(也称为opname),每一个都是在简要介绍dis模块及其实施中可以找到ceval.c(CPython的核心环)
  6. Python 内部使用的指令的参数(如果有),用于获取一些常量或变量、管理堆栈、跳转到特定指令等。
  7. 指令参数的人性化解释