Python 是解释型的,还是编译型的,或者两者兼而有之?
- 2025-01-15 08:46:00
- admin 原创
- 93
问题描述:
据我了解:
解释型语言是一种由解释器(将高级语言转换为机器代码然后执行的程序)随时运行和执行的高级语言;它一次处理一点程序。
编译型语言是一种高级语言,其代码首先由编译器(将高级语言转换为机器代码的程序)转换为机器代码,然后由执行器(运行代码的另一个程序)执行。
如果我的定义错误,请纠正我。
现在回到 Python,我对此有点困惑。到处你都会了解到 Python 是一种解释型语言,但它被解释为某种中间代码(如字节码或 IL),而不是机器码。那么哪个程序会执行 IM 代码?请帮助我理解 Python 脚本是如何处理和运行的。
解决方案 1:
首先,解释/编译不是语言的属性,而是实现的属性。对于大多数语言来说,大多数(如果不是全部)实现都属于同一类别,因此人们可能会省去几句话说语言也是解释/编译的,但这仍然是一个重要的区别,因为它有助于理解,也因为有相当多的语言具有两种类型的可用实现(主要是在函数式语言领域,参见 Haskell 和 ML)。此外,还有 C 解释器和项目试图将 Python 子集编译为 C 或 C++ 代码(随后编译为机器代码)。
其次,编译并不局限于提前编译为本机机器代码。更一般地说,编译器是一种将一种编程语言的程序转换为另一种编程语言的程序的程序(可以说,如果应用了重大转换,甚至可以拥有具有相同输入和输出语言的编译器)。而 JIT 编译器在运行时编译为本机机器代码,这可以使速度非常接近甚至优于提前编译(取决于基准和比较的实现的质量)。
但是,不要再吹毛求疵了,回答你想问的问题:实际上(阅读:使用一种比较流行和成熟的实现),Python 是编译的。不是提前编译为机器码(即由受限制和错误但可惜是通用的定义“编译”),“仅”编译为字节码,但它仍然是编译,至少具有一些优点。例如,语句a = b.c()
被编译为字节流,当“反汇编”时,看起来有点像load 0 (b); load_str 'c'; get_attr; call_function 0; store 1 (a)
。这是一种简化,它实际上可读性较差,而且有点低级 - 您可以尝试使用标准库dis
模块,看看真正的交易是什么样的。解释它比从更高级别的表示进行解释要快。
该字节码要么被解释(请注意,在理论和实际性能上,直接解释和先编译为某个中间表示并对其进行解释是有区别的),就像参考实现(CPython)一样,要么在运行时被解释和编译为优化的机器码,就像PyPy一样。
解决方案 2:
CPU 确实只能理解机器代码。对于解释型程序来说,解释器的最终目的就是将程序代码“解释”成机器代码。但现代解释型语言通常不会直接解释人类代码,因为效率太低。
Python 解释器首先读取人类代码并将其优化为一些中间代码,然后再将其解释为机器代码。这就是为什么你总是需要另一个程序来运行 Python 脚本,而不像在 C++ 中你可以直接运行代码的编译可执行文件。例如,c:Python27python.exe
或/usr/bin/python
。
解决方案 3:
答案取决于正在使用哪种 Python 实现。如果您使用的是CPython(Python 的标准实现)或Jython(旨在与 Java 编程语言集成),它首先被翻译成字节码,然后根据您使用的 Python 实现,此字节码将被定向到相应的虚拟机进行解释。CPython为PVM(Python 虚拟机),Jython为 JVM(Java 虚拟机)。
但是假设你正在使用PyPy,这是另一个标准 CPython 实现。它将使用即时编译器。
解决方案 4:
是的,它既是编译型语言,又是解释型语言。 那为什么我们一般称它为解释型语言呢?
看看它是如何编译和解释的?
首先我想说的是,如果您来自 Java 世界,您会更喜欢我的回答。
在 Java 中,源代码首先通过javac编译器转换为字节码,然后定向到JVM(负责生成用于执行的本机代码)。现在我想向您展示我们将 Java 称为编译语言,因为我们可以看到它确实编译了源代码并通过以下方式提供.class文件(只有字节码):
javac Hello.java -------> 生成Hello.class文件
java Hello -------->将字节码导向JVM进行执行
同样的事情也发生在 Python 上,即源代码首先通过编译器转换为字节码,然后定向到PVM(负责生成用于执行的本机代码)。现在我想向您展示,我们通常将 Python 称为解释型语言,因为编译发生在后台
,当我们运行 Python 代码时:
python Hello.py ------->直接执行代码,只要代码语法正确,我们就可以看到输出
@ python Hello.py看起来像是直接执行,但实际上它首先生成字节码,该字节码由解释器解释,以生成用于执行目的的本机代码。
CPython——负责编译和解释。
如果您需要更多详细信息,请查看以下内容:
正如我提到的,CPython编译源代码,但实际的编译是在cython的帮助下进行的,然后在CPython的帮助下进行解释
现在我们来谈谈 Java 和 Python 中即时编译器的作用
JVM 中存在 Java 解释器,它逐行解释字节码以获取本机机器代码以供执行,但是当 Java 字节码由解释器执行时,执行速度总是较慢。那么解决方案是什么?解决方案是即时编译器,它生成本机代码,其执行速度比解释速度快得多。一些 JVM 供应商使用Java 解释器,一些使用即时编译器。参考:单击此处
在 Python 中,为了绕过解释器实现快速执行,请使用另一个 Python 实现(PyPy)而不是CPython。
单击此处查看包括PyPy在内的其他 Python 实现。
解决方案 5:
根据 Python 官方网站,它是解释性的。
https://www.python.org/doc/essays/blurb/
Python 是一种解释型、面向对象的高级编程语言......
...
由于没有编译步骤...
...
Python 解释器和广泛的标准库可用...
...
相反,当解释器发现错误时,它会引发异常。当程序没有捕获异常时,解释器会打印堆栈跟踪。
解决方案 6:
对于刚开始使用 Python 的人来说,这是一个很大的困惑,这里的答案有点难以理解,所以我会让它变得更容易一些。
当我们指示 Python 运行脚本时,在代码实际开始执行之前,Python 会执行几个步骤:
它被编译为字节码。
然后它被路由到虚拟机。
当我们执行一些源代码时,Python 会将其编译为字节码。编译是一个翻译步骤,字节码是源代码的低级平台独立表示。
请注意,Python 字节码不是二进制机器码(例如,英特尔芯片的指令)。
实际上,Python 将源代码中的每个语句分解为单独的步骤,将其转换为字节码指令。执行字节码转换是为了加快执行速度。字节码的运行速度比原始源代码语句快得多。它具有 .pyc 扩展名,如果它可以写入我们的机器,它就会被写入。
因此,下次我们运行同一个程序时,Python 将加载 .pyc 文件并跳过编译步骤,除非它已被更改。Python 会自动检查源代码和字节码文件的时间戳,以了解何时必须重新编译。如果我们重新保存源代码,则下次运行程序时会自动再次创建字节码。
如果 Python 无法将字节码文件写入我们的机器,我们的程序仍然可以运行。字节码在内存中生成,并在程序退出时被丢弃。但由于 .pyc 文件可以加快启动时间,我们可能需要确保它是为较大的程序编写的。
让我们总结一下幕后发生的事情。
当 Python 执行程序时,Python 将 .py 读入内存,并对其进行解析以获取字节码,然后继续执行。对于程序导入的每个模块,Python 首先检查 .pyo 或 .pyc 中是否有预编译的字节码版本,该版本具有与其 .py 文件相对应的时间戳。如果有,Python 将使用该字节码版本。否则,它会解析模块的 .py 文件,将其保存到 .pyc 文件中,并使用刚刚创建的字节码。
字节码文件也是传输 Python 代码的一种方式。如果 Python 能找到的都是 .pyc 文件,即使没有原始 .py 源文件,它仍会运行程序。
Python 虚拟机 (PVM)
一旦我们的程序被编译成字节码,它就会被发送到 Python 虚拟机 (PVM) 执行。PVM 不是一个单独的程序。它不需要单独安装。实际上,PVM 只是一个大循环,它逐一遍历我们的字节码指令来执行它们的操作。PVM 是 Python 的运行时引擎。它始终作为 Python 系统的一部分存在。它是真正运行我们脚本的组件。从技术上讲,它只是所谓的 Python 解释器的最后一步。
解决方案 7:
如果 (您了解 Java) {
Python 代码像 Java 一样转换为字节码。
每次您尝试访问它时,都会再次执行该字节码。
} else {
Python 代码最初被翻译成一种称为字节码的东西
,它非常接近机器语言,但不是真正的机器码,
所以每次我们访问或运行它时,都会再次执行该字节码
}
解决方案 8:
这实际上取决于所用语言的实现!不过,任何实现都有一个共同的步骤:首先将代码编译(翻译)为中间代码 - 介于代码和机器(二进制)代码之间的代码 - 称为字节码(存储在 .pyc 文件中)。请注意,这是一个一次性步骤,除非您修改代码,否则不会重复执行。
每次运行程序时,都会执行该字节码。如何执行?好吧,当我们运行程序时,此字节码(在 .pyc 文件内)将作为输入传递给虚拟机 (VM) 1 - 允许执行我们程序的运行时引擎 - 并执行它。
根据语言实现,VM 将解释字节码(对于 CPython 2实现),或者对其进行 JIT 编译(对于 PyPy 4实现)。
注意:
1计算机系统的仿真
2字节码解释器;该语言的参考实现,用 C 和 Python 编写 - 使用最广泛
3在程序执行期间(运行时)进行的编译
4字节码 JIT 编译器;CPython 的替代实现,用 RPython(受限 Python)编写 - 通常比 CPython 运行得更快
解决方案 9:
对于新手
在运行脚本之前,Python 会自动将其编译为编译代码,即所谓的字节码。
运行脚本不被视为导入,并且不会创建 .pyc。
例如,如果您有一个脚本文件 abc.py,它导入了另一个模块 xyz.py,当您运行 abc.py 时,由于 xyz 已被导入,所以会创建 xyz.pyc,但由于 abc.py 未被导入,所以不会创建 abc.pyc 文件。
解决方案 10:
几乎可以说,Python 是解释型语言。但是我们使用 Python 中的一次性编译过程的一部分将完整的源代码转换为字节码,就像 Java 语言一样。
解决方案 11:
陈述
Python 是一种解释型语言,这一点毋庸置疑。即使 Python 将代码“编译”成字节码,这也不是一个完整的编译过程,除此之外,Python 不会将所有代码(值和类型)“编译”成字节码。我的分析针对以下代码进行:
用于测试语句正确性的框架
import time
import dis
import main
def error():
print("
#############")
print("## ERROR ##")
print("#############
")
syntax_error # <==== Syntax error
syntax_error # <==== Syntax error
def init():
# GET THE INITIAL TIME
t = time.time()
while True:
elapsed_time = time.time() - t
if elapsed_time > 10:
print("
ELAPSED TIME: " + str(int(elapsed_time)) + "
")
error()
if __name__ == "__main__":
# PRINT THE GENERATED BYTECODE FOR THE CURRENT PROGRAM FILE
print("
##########################")
print("## GENERATED BYTECODE ##")
print("##########################
")
print(str(dis.dis(main)) + "
")
init()
上述代码将打印为文件生成的字节代码,然后获取循环启动的初始时间。当生成上述循环时,它将检查自设置初始时间以来经过的时间,当经过的时间大于 10 秒时,它将调用包含语法错误的方法。
测试结果
用于测试该语句的代码的生成的字节码如上所示。在上面显示的字节码中,字节码包含的属性是常量名称、方法名称、方法调用指令和简单返回指令。因此,Lexer
Python 的字节码“编译器”不会检查变量的值和类型。
如上图所示,编译器将不存在的变量视为syntax_error
变量。真正的编译语言在编译时会执行类型检查和值检查。
另外,如上图所示的“编译”过程本身正在创建对一些常量的引用,它还创建了一些有关它们在内存中的分配的指令以及创建了一些用于方法调用的引用,而不是创建有关它们的分配的指令、创建命名引用、精确的内存分配指令和有关方法内代码执行的精确操作指令。
例如,如果我们查看生成的字节码中方法print("## ERROR ##")
内的打印语句error()
,则有关代码的指令和引用非常简单。创建对方法的引用,然后创建对要打印的字符串的引用,然后创建print()
有关必须调用该方法的指令。print()
当使用 C# 等JIT(即时)语言执行相同操作时,生成的IL(“中间语言”,字节码的 C# 版本)中的结果使用保留关键字指定方法主体内容以哪种代码语言形式表示cil managed
,并且指定方法名称必须隐藏,因为它与使用保留关键字的派生类中另一个方法的方法名称重叠hidebysig
。C# IL代码还使用关键字和值指定方法内部要在堆栈上分配的最大元素数量为 8 .maxstack 8
,还使用关键字指定不执行任何操作的点nop
,还使用关键字指定对于字符串,## ERROR ##
必须在 RAM 内存中创建一个字符串对象才能存储对象ldstr
,还使用关键字指定Console.WriteLine()
通过使用关键字调用其程序集来调用方法call
,还使用关键字指定方法应何时返回控件ret
。与 Python 函数等效的 C# 代码用于直接比较真正的编译语言(C#)和非编译语言(Python)。指令数量、复杂性和细节的差异不言而喻。在 C# 中,甚至在IL中指定了要在堆栈上分配的内存量,而在 Python 的字节码中,甚至没有为每个方法指定方法返回指令。
由于上述多个事实,当 Python 程序运行时,程序启动 10 秒后,程序将调用该error()
方法并产生错误,而不是在编译时捕获错误。
结论
根据提供的代码的测试用例场景结果,Python 并不是真正意义上的编译型语言。Python 执行的字节码翻译过程有助于解释器更快地执行代码,因为从处理的角度来看,代码被简化为资源消耗较少的代码形式。这意味着解释器将字节码转换为汇编语言的工作量较少,而不是直接将 Python 源代码转换为汇编语言 CPU 指令。Python 不是编译型语言的另一个原因是,编译过程发生在解释之前,而不是只发生一次。
解决方案 12:
你编写的 Python 代码会被编译成 Python 字节码,并创建扩展名为 .pyc 的文件。如果可以编译,那么问题又来了,为什么不编译语言呢?
请注意,这不是传统意义上的编译。通常,我们会说编译是将高级语言转换为机器代码。但它是一种编译。编译成中间代码而不是机器代码(希望你现在明白了)。
回到执行过程,在编译步骤中创建的 pyc 文件中的字节码随后由适当的虚拟机执行,在我们的例子中是 CPython VM。时间戳(称为魔法数字)用于验证 .py 文件是否已更改,具体取决于是否创建了新的 pyc 文件。如果 pyc 是当前代码,则它直接跳过编译步骤。
解决方案 13:
Python(解释器)是经过编译的。
证明:如果您的代码包含语法错误,它甚至不会编译它。
示例 1:
print("This should print")
a = 9/0
输出:
This should print
Traceback (most recent call last):
File "p.py", line 2, in <module>
a = 9/0
ZeroDivisionError: integer division or modulo by zero
代码编译成功。第一行执行(
ZeroDivisionError
(运行时错误)。
示例 2:
print("This should not print")
/0
输出:
File "p.py", line 2
/0
^
SyntaxError: invalid syntax
结论:如果您的代码文件不包含
SyntaxError
任何内容,则编译将失败。
解决方案 14:
正如有人所说,“解释/编译不是语言的属性,而是实现的属性。” Python 既可以在解释模式中使用,也可以在编译模式下使用。当您直接从终端或 cmd 运行 Python 代码时,Python 解释器就会启动。现在,如果您编写任何命令,则该命令将被直接解释。如果您使用包含 Python 代码的文件并在 IDE 中运行它或使用命令提示符,它将首先被编译,整个代码将转换为字节码,然后它将运行。所以这取决于我们如何使用它。
解决方案 15:
似乎是语义问题。我认为我们大多数人都推断编译的通常结果是机器代码。考虑到这一点,我对自己说 Python 不是编译的。但我错了,因为编译实际上意味着转换为较低级别,因此从源代码转换为字节码也是编译。
解决方案 16:
我认为 Python 属于解释器类别,因为它被设计为能够完全处理(从 Python 代码到 CPU 执行)单个 Python 语句。即,您编写一条语句并可以执行它,如果没有错误,则会得到相应的结果。
我认为,拥有中间代码(如字节码)与将其归类为编译器没有区别。虽然此组件(中间代码生成)通常是编译器的一部分,但它也可以用于解释器。请参阅维基百科对解释器的定义https://en.m.wikipedia.org/wiki/Interpreter_(computing)。它是提高执行速度效率的关键部分。有了缓存,它就更加强大了,如果你没有在当前程序范围内更改代码,你就可以跳过繁重的处理步骤,如词法、语义分析甚至一些代码优化。