Python 是一种 “编译型语言”
导读:学习使用了多年的Python,其实它是一种编译型语言?
这篇文章的目标是告诉各位同学,我们用的 Python 本质是一种“编译型语言”。
我这里所说的“Python”并不是指 PyPy、Mypyc、Numba、Cinder等 Python 的替代版本,也不是Cython、Codon、Mojo 1等“类 Python ”的语言。
这里指的是正常的 Python,使用CPython纯官方编译器的内核的“正版”。
可能你的电脑上已经安装了 Python。当你用搜索引擎搜索“python”并下载第一个出现的东西时,这就是属于你的Python。你只需在新的命令行中输入即可启动 Python 。
对,那并不是一条蟒蛇,而是一门编程语言,而 Python 确确实实还是一种“编译型编程语言”。
一些前置的技术背景
目前,我正在编写教学生如何阅读和理解编程错误消息的课程材料。
我正在为3种编程语言创建课程:C、Python 和 Java。不管是什么语言,教授错误消息这个地方时,其本质的关键点之一是它们是在不同阶段生成的 :一些错误消息在编译时报告,而另一些错误消息在程序主动运行时报告。
第一课我是为 C 语言编写的,特别是使用 GCC 编译器。这些经验教训表明,GCC 将把代码转变为正在运行的程序的任务分为以下不同的阶段:
预处理
词法分析
句法分析
语义分析
LINK链接
此外,课程会和同学们讨论每个阶段可能发生的错误,以及这些错误将如何影响所显示的错误消息。重要的是,早期阶段的错误将阻止后面阶段检测的错误。
当我针对 Java 和 Python 改编本课程时,我意识到有些事情必须改变。例如,Python 或 Java 都没有预处理器,并且“链接”在 Python 或 Java 中实际上并不是同一件事,所以我跳过了这些主题。
但是,我偶然间发现了一个有趣的总结。
错误消息可以帮助我们发现编译的不同阶段
事实上,错误消息是由编译器的不同阶段生成的,并且编译器通常在继续运行之前从早期阶段发出错误,这也表示我们可以通过在程序中故意创建错误来发现编译器的阶段。
让我们通过玩一个我喜欢称为小游戏的来探索 Python 解释器的各个阶段......
哪个是这个第一错误!
我们将创建一个带有多个错误的Python 程序,每个错误都会尝试引发不同类型的错误消息。我们知道常规的Python 一次只报告一条错误消息,所以游戏是先报告哪条错误消息?
这是有错误的程序:
1 / 0
print() = None
if False
ñ = "hello
每行代码都会生成不同的错误消息:
1 / 0 将生成ZeroDivisionError: division by zero.
print() = None 将生成SyntaxError: cannot assign to function call.
if False 将生成SyntaxError: expected ':'.
ñ = "hello 将生成SyntaxError: EOL while scanning string literal.
问题是哪个会先被报出?本文中除非另有说明,我的报告将来自 Python 3.12 的错误消息。说明:Python 的特定版本很重要(比我想象的更重要),所以如果你看到不同的结果,请先记住这一点。
第1轮
现在知道了规则和期望的错误消息,让我们开始!先考虑如果不运行代码,会先报上面哪个错误信息?
在揭晓答案之前,请先想一下“解释型”语言与“编译型”语言对你意味着什么。这是一段苏格拉底式对话,希望能让大家反思其中的区别:
苏格拉底:编译语言是代码首先通过编译器才能运行的语言。C 语言就是一个例子。要运行 C 代码,首先必须运行类似gcc或 的 编译器clang,然后最后才能运行代码。编译语言被转换为二进制机器代码——CPU 可以理解的 1 和 0。
Plato:但是等等,Java 不是一种编译语言吗?
苏格拉底:是的,Java是一种编译语言。
Plato:但是常规 Java 编译器的输出不是.class文件。那是字节码,不是吗?
苏格拉底:是的。字节码不是机器代码,但 Java 仍然是一种编译语言。这是因为编译器可以捕获许多问题,因此你需要在程序开始运行之前更正完错误。
柏拉图:什么是解释性语言呢?
苏格拉底:解释性语言是一种依赖于单独的程序(恰当地称应该叫解释器)来实际运行代码的语言。解释型语言不需要完全编译后就运行。因此在程序运行时犯的任何错误都将被捕获。Python 就是一种解释性语言——没有单独的编译器,并且你犯的所有错误都会在运行时捕获。
Plato:如果 Python 不是编译语言,那么为什么标准库包含名为 py_compile和 的模块compileall?
苏格拉底:嗯,这些模块只是将 Python 转换为字节码。它们不会将 Python 转换为机器代码,因此 Python 仍然是一种解释性语言。
Plato:那么,Python 和 Java 都会转换为字节码吗?
苏格拉底:正确。
Plato:那么为什么 Python 是解释性语言,而 Java 却是编译性语言呢?
苏格拉底:因为Python中的所有错误都是在运行时捕获的。
好了,差不多够了。让我们来看看实际的答案。如果在 Python 3.12 中运行上面的代码...
🥁🥁🥁🥁
你将收到类似的错误消息:
File "/private/tmp/round_1.py", line 4
ñ = "hello # SyntaxError: EOL while scanning string literal
^
SyntaxError: unterminated string literal (detected at line 4)
检测到的第一条错误消息位于源代码的最后一行。这告诉我们, Python在运行第一行代码之前必须读取整个源代码文件。如果你脑子里对“解释性语言”有一个定义,其中包括“解释性语言一次运行一行代码”,那么我希望你把它划掉!
这里发生了什么?
我还没有深入研究 CPython 解释器的源代码来验证这一点,但我认为这是检测到的第一个错误的原因是 Python 3.12 所做的第一步是扫描(也称为词法分析)分析)。扫描器将整个文件转换为一系列标记,然后继续下一阶段。字符串文字末尾缺少引号是扫描器检测到的错误 - 扫描器想要将整个字符串转换为一个大标记,但在找到结束引号之前它无法做到这一点。在 Python 3.12 中,扫描器先运行,然后再运行,因此这是第一条错误消息。
让我们关闭第 4 行的引号以消除该错误消息,然后重试一下。
第二轮
修复了第一个错误后,我们的代码修改如下所示:
1 / 0
print() = None
if False
ñ = "hello"
第 1、2、3 行仍然有错误。
哪个!是!这!第一的!错误!
对第一个报告的错误进行有根据的猜测。这次我就不给你们讲稻草人式的苏格拉底式对话了,直接进入正题。
🥁🥁🥁🥁
File "/private/tmp/round_2.py", line 2
print() = None
^^^^^^^
SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='?
这里发生了什么?
我没有检查 CPython 的源代码,但我有理由确定下一阶段是解析(也称为语法分析)并且解析器报告源代码中的第一个错误。解析整个文件发生在运行第一行代码之前,这意味着 Python 甚至看不到第 1 行的错误并报告第 2 行的语法错误。
在这一点上,我现在要指出,我为本练习编写的程序完全是无意义的,并且对于如何修复错误没有正确的答案。我不知道 的意图是什么 print() = None,所以我将通过将其替换为来解决这个问题print(None) ,这也没有具体意义,但至少它在语法上是正确的。
第三轮
我们修复了一个语法错误,但文件中还存在另外两个错误,其中一个也是语法错误。我们的文件如下所示: