Compiler construction Python编译/解释过程
我试图更清楚地理解python编译器/解释器过程。不幸的是,我没有上过口译员课程,也没有读过很多关于口译员的书 基本上,我现在理解的是,来自.py文件的Python代码首先被编译成Python字节码(我假设偶尔看到的是.pyc文件)。接下来,字节码被编译成机器码,一种处理器真正理解的语言。 差不多,我读过这篇文章 有人能给我一个很好的解释整个过程吗?记住我对编译器/口译员的知识几乎是不存在的?或者,如果不可能,可以给我一些资源,让我快速概述编译器/解释器Compiler construction Python编译/解释过程,compiler-construction,interpreter,python,Compiler Construction,Interpreter,Python,我试图更清楚地理解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值||
每一列都有特定的用途:
跳转
您不会“解释为机器代码”——编译器就是这样做的。Python解释器只是执行字节码。(字节码是.pyc。)另一方面,您可能会发现知道原始.py文件的最后一次修改时间是在.pyc文件中编码的,这很有帮助。这使Python能够确定是否需要创建新的.pyc文件。当然,.pyc文件的目的是避免每次调用脚本时都解析整个脚本。如果使用.pyc,Python程序将不会运行得更快。只有加载时间发生变化。解释器/VM是C语言。它是一个循环,使用当前字节在一个巨大的switch语句中选择多种情况之一。在交换机中间的某个地方,有一个
def f(num):
if num == 42:
return True
return False