Compiler construction Python编译/解释过程

Compiler construction Python编译/解释过程,compiler-construction,interpreter,python,Compiler Construction,Interpreter,Python,我试图更清楚地理解python编译器/解释器过程。不幸的是,我没有上过口译员课程,也没有读过很多关于口译员的书 基本上,我现在理解的是,来自.py文件的Python代码首先被编译成Python字节码(我假设偶尔看到的是.pyc文件)。接下来,字节码被编译成机器码,一种处理器真正理解的语言。 差不多,我读过这篇文章 有人能给我一个很好的解释整个过程吗?记住我对编译器/口译员的知识几乎是不存在的?或者,如果不可能,可以给我一些资源,让我快速概述编译器/解释器 谢谢,字节码实际上并没有被解释为机器代码

我试图更清楚地理解python编译器/解释器过程。不幸的是,我没有上过口译员课程,也没有读过很多关于口译员的书

基本上,我现在理解的是,来自.py文件的Python代码首先被编译成Python字节码(我假设偶尔看到的是.pyc文件)。接下来,字节码被编译成机器码,一种处理器真正理解的语言。 差不多,我读过这篇文章

有人能给我一个很好的解释整个过程吗?记住我对编译器/口译员的知识几乎是不存在的?或者,如果不可能,可以给我一些资源,让我快速概述编译器/解释器


谢谢,字节码实际上并没有被解释为机器代码,除非您使用一些特殊的实现,比如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        
>>> 
下一条指令,
COMPARE_OP 0
,告诉解释器弹出最上面的两个堆栈元素,并在它们之间执行不等式比较,将布尔结果推回到堆栈上。第四条指令根据布尔值确定是向前跳转五条指令还是继续执行下一条指令。所有这些措辞解释了
fib
中条件表达式的
if n<2
部分。对你来说,梳理出
fib
字节码其余部分的含义和行为将是一个非常有启发性的练习。唯一一个,我不确定的是
POP_TOP
;我猜
JUMP\u 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'
>>> 
因此,您可以看到字节码的第一个字节是
LOAD\u FAST
指令。下一对字节,
'\x00\x00'
(16位中的数字0)是
LOAD\u FAST
的参数,并告诉字节码解释器将参数0加载到堆栈上。

为了完成这个伟大的过程,这里只提供一个小的逐列摘要来解释反汇编字节码的输出

例如,给定此函数:

def f(num):
    if num == 42:
        return True
    return False
这可以分解为(Python 3.6):

(1)|(2)|(3)|(4)|(5)|(6)|(7)
---|---|---|---|----------------------|---|-------
2 | | | 0 |快速加载| 0 |(num)
|-->|| 2 |荷载常数| 1 |(42)
|| | 4 |比较| OP | 2 |(==)
|| | 6 |砰|跳|如果|假| 12|
|   |   |   |                      |   |
3 | | | 8 |荷载常数| 2 |(正确)
|| | 10 |返回| U值||
|   |   |   |                      |   |
4 | |>| 12 |加载常数| 3 |(假)
|| | 14 |返回| U值||
每一列都有特定的用途:

  • 源代码中相应的行号
  • 可选地指示执行的当前指令(例如,当字节码来自时)
  • 一种标签,表示从早期指令到本指令可能的
    跳转
  • 字节码中与字节索引相对应的地址(这些是2的倍数,因为Python 3.6对每条指令使用2个字节,而在以前的版本中可能有所不同)
  • 指令名(也称为opname),在中对每一条指令进行了简要说明,它们的实现可以在(CPython的核心循环)中找到
  • 指令的参数(如果有),Python在内部使用它来获取一些常量或变量、管理堆栈、跳转到特定指令等
  • 教学论证的人性化解释

  • 您不会“解释为机器代码”——编译器就是这样做的。Python解释器只是执行字节码。(字节码是.pyc。)另一方面,您可能会发现知道原始.py文件的最后一次修改时间是在.pyc文件中编码的,这很有帮助。这使Python能够确定是否需要创建新的.pyc文件。当然,.pyc文件的目的是避免每次调用脚本时都解析整个脚本。如果使用.pyc,Python程序将不会运行得更快。只有加载时间发生变化。解释器/VM是C语言。它是一个循环,使用当前字节在一个巨大的switch语句中选择多种情况之一。在交换机中间的某个地方,有一个 Load LoopFix:接着是读取下两个字节的代码,在一些“参数”集合中查找指定的参数,并将其推送到堆栈对象上。为了与外部世界进行交互,Python允许调用扩展模块,这些模块的行为类似于Python代码和对象,但实际上是编译代码,因此可以代表您的脚本直接与图形卡等进行对话。请更明确地回答最后一个问题:“与图形卡对话”没有Python操作码。“在此模块中调用此函数”有一个操作码,如果该模块是图形编程扩展模块,解释器将调用所请求函数的库入口点,并向其传递一些参数。C库(假设它是C)梳理出参数,将它们从Python对象转换成C值和结构,并将调用转发到真正的图形库中,然后该图形库将一个彩色的三角形
    def f(num):
        if num == 42:
            return True
        return False