网易首页 > 网易号 > 正文 申请入驻

Python 既是解释型语言,也是编译型语言

0
分享至

​大家好,我是猫哥,我在 中分享了一则对自己很有启发性的文章,昨天在电报频道也分享了该文章引用到的两则材料:

我原想着抽空把这篇文章翻译出来的,没想到这么快已经有译文版本发布了!因此我就偷个小懒,直接转载过来吧。(原文中引用的两则材料也很值得一读!)

本文文字较多,干货满满,耐心看完相信你会有不小的收获

原文:https://eddieantonio.ca/blog/2023/10/25/python-is-a-compiled-language/

译者:咸鱼运维杂谈

前 言

本文所说的 Python ,不是指 PyPy、Mypyc、Numba、Cinder 等 Python 的替代版本,也不是像 Cython、Codon、mojo1这样的类 Python 编程语言

我指的是常规的 Python——CPython

目前,我正在编写一份教材,教学生如何阅读和理解程序报错信息(programming error messages)。我们正在为三种编程语言(C、Python、Java)开设课程

程序报错信息的本质的关键点之一在于程序报错是在不同阶段生成的,有些是在编译时生成,有些是在运行时生成

第一门课是针对 C 语言的,具体来说是如何使用 GCC 编译器,以及演示 GCC 如何将代码转换成可执行程序

  • 预处理(preprocessing)

  • 词汇分析(lexical analysis)

  • 语法分析(syntactic analysis)

  • 语义分析(semantic analysis)

  • 链接(linking)

除此之外,这节课还讨论了在上述阶段可能出现的程序报错,以及这些报错将如何影响所呈现的错误消息。重要的是:早期阶段的错误将阻止在后期阶段检测到错误(也就是说 A 阶段的报错出现之后,B 阶段就算有错误也不会检测出来)

当我将这门课调整成针对 Java 和 Python 时,我发现 Python 和 Java 都没有预处理器(preprocessor),并且 Python 和 Java 的链接(linking)不是同一个概念

我忽略了上面这些变化,但是我偶然发现了一个有趣的现象:

编译器在各个阶段会生成报错信息,而且编译器通常会在继续执行之前把前面阶段的报错显示出来,这就意味着我们可以通过在程序中故意创建错误来发现编译器的各个阶段

所以让我们玩一个小游戏来发现 Python 解释器的各个阶段

Which is the first error ?

我们将创建一个包含多个 bug 的 Python 程序,每个 bug 都试图引发不同类型的报错信息

我们知道常规的 Python 每次运行只会报告一个错误,所以这个游戏就是——哪条报错会被首先触发

每行代码都会产生不同的报错:

  • 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 版本很重要(比我想象的要重要),所以如果你看到不同的结果,请记住这一点

PS:下面运行代码所使用的 Python 版本为 Python 3.12

在开始执行代码之前,先想想【解释】语言和【编译】语言对你来说意味着什么?

下面我将给出一段苏格拉底式的对话,希望你能反思一下其中的区别

苏格拉底:编译语言是指代码在运行之前首先通过编译器的语言。一个例子是 C 编程语言。要运行 C 代码,首先必须运行像 orclang这样的gcc编译器,然后才能运行代码。编译后的语言被转换为机器代码,即 CPU 可以理解的 1 和 0。

柏拉图:等等,Java不是一种编译语言吗?

苏格拉底:是的,Java是一种编译语言。

柏拉图:但是常规 Java编译器的输出不是一个.class文件。那是字节码,不是吗?

苏格拉底:没错。字节码不是机器码,但 Java 仍然是一种编译语言。这是因为编译器可以捕获许多问题,因此您需要在程序开始运行之前更正错误。

柏拉图:解释型语言呢?

苏格拉底:解释型语言是依赖于一个单独的程序(恰如其分地称为解释器)来实际运行代码的语言。解释型语言不需要程序员先运行编译器。因此,在程序运行时,您犯的任何错误都会被捕获。Python 是一种解释型语言,没有单独的编译器,您犯的所有错误都会在运行时捕获。

柏拉图:如果 Python不是一种编译语言,那么为什么标准库包含名为py_compileandcompileall的模块?

苏格拉底:嗯,这些模块只是将 Python转换为字节码。他们不会将 Python 转换为机器代码,因此 Python 仍然是一种解释型语言。

柏拉图:那么,Python和 Java都转换为字节码了吗?

苏格拉底:对。

柏拉图:那么,为什么Python是一种解释型语言,而 Java却是一种编译型语言呢?

苏格拉底:因为 Python 中的所有错误都是在运行时捕获的。 (ps:请注意这句话)

  • 回合一

当我们执行上面那段有 bug 的程序时,将会收到下面的错误

检测到的第一个报错位于源码的最后一行。可以看到:在运行第一行代码之前,Python 必须读取整个源码文件

如果你脑子里有一个关于【解释型语言】的定义,其中包括”解释型语言按顺序读取代码,一次运行一行”,我希望你忘掉它

我还没有深入研究 CPython 解释器的源码来验证这一点,但我认为这是第一个检测到的报错的原因是Python 3.12 所做的第一个步骤是扫描(scanning ),也称为词法分析

扫描器将整个文件转换为一系列标记(token),然后继续进行下一阶段。

扫描器扫描到源码最后一行的字符串字面值末尾少了个引号,它希望把整个字符串字面值转换成一个 token ,但是没有结束引号它就转换不了

在 Python 3.12 中,扫描器首先运行,所以这也是为什么第一个报错是unterminated string literal

  • 回合二

我们把第四行的代码的 bug 修复好,第 1 2 3 行仍有 bug

我们现在来执行代码,看下哪个会首先报错

这次是第二行报错!同样,我没有去查看 CPython 的源码,但是我有理由确定扫描的下一阶段是解析(parsing),也称为语法分析

在运行代码之前会先解析源码,这意味着 Python 不会看到第一行的错误,而是在第二行报错

我要指出我为这个小游戏而编写的代码是完全没有意义的,并且对于如何修复 bug 也没有正确的答案。我的目的纯粹是编写错误然后发现 python 解释器现在处在哪个阶段

我不知道print() = None可能是什么意思,所以我将通过将其替换为print(None)来解决这个问题,这也没有意义,但至少它在语法上是正确的。

  • 回合三

我们把第二行的语法错误也修复了,但源码还有另外两个错误,其中一个也是语法错误

回想一下,语法错误在回合二的时候优先显示了出来,在回合三还会一样吗

没错!第三行的语法错误优先于第一行的错误

正如回合二一样,Python 解释器在运行代码之前会先解析源码,对其进行语法分析

这意味着 Python 不会看到第一行的错误,而是在第三行报错

你可能想知道为什么我在一个文件中插入了两个SyntaxError,难道一个还不够表明我的观点吗?

这是因为 Python 版本的不同会导致结果的不同,如果你在 Python3.8 或者更早的版本去运行代码,那么结果如下

在 Python 3.8 中,第 2 轮报告的第一个错误消息位于第 3 行:

修复第三行的错误之后,Python 3.8 在第 2 行报告以下错误消息:

为什么 Python 3.8 和 3.12 报错顺序不一样?是因为 Python 3.9 引入了一个新的解析器。这个解析器比以前的naïve解析器功能更强大

旧的解析器无法提前查看多个 token,这意味着旧解析器在技术上可以接受语法无效的 Python 程序

尤其是这种限制导致解析器无法识别赋值语句的左边是否为有效的赋值目标,好比下面这段代码,旧解析器能够接收下面的代码

上面这段代码没有任何意义,甚至 Python 语法是不允许这么使用的。为了解决这个问题,Python 曾经存在过一个独立的,hacky 的阶段(这个 hacky 我不知道用什么翻译比较好)

Python会检查所有的赋值语句,并确保赋值号左边实际上是可以被赋值的东西

而这个阶段是发生在解析之后,这也就是为什么旧版本 Python 中会先把第二行的报错先显示出来

  • 回合四

现在还剩最后一个错误了

我们来运行一下

需要注意的是,Traceback (most recent call last)表示 Python 运行时报错的主要内容,这里在回合四才出现

经过前面的扫描、解析阶段,Python 终于能够运行代码了。但是当 Python 开始运行解释第一行的时候,引发一个名为ZeroDivisionError的报错

为什么知道现在处于【运行时】,因为 Python 已经打印出Traceback (most recent call last),这表示我们有一个堆栈跟踪

堆栈跟踪只能在运行时存在,这意味着这个报错必须在运行时捕获。

但这意味着在回合1~3 中遇到的报错不是运行时报错,那它们是什么报错?

Python 既是解释型语言,也是编译型语言

没错!CPython 解释器实际上是一个解释器,但它也是一个编译器

我希望上面的练习已经说明了 Python 在运行第一行代码之前必须经过几个阶段:

  • 扫描(scanning )

  • 解析(parsing )

旧版本的 Python 多了一个额外阶段:

  • 扫描(scanning )

  • 解析(parsing )

  • 检查有效的分配目标(checking for valid assignment targets)

让我们将其与前面编译 C 程序的阶段进行比较:

  • 预处理

  • 词汇分析(“扫描”的另一个术语)

  • 语法分析(“解析”的另一个术语)

  • 语义分析

  • 链接

Python 在运行任何代码之前仍然执行一些编译阶段,就像 Java一样,它会把源码编译成字节码

前面三个报错是 Python 在编译阶段产生的,只有最后一个才是在运行时产生,即ZeroDivisionError: division by zero.

实际上,我们可以使用命令行上的compileall模块预先编译所有 Python 代码:

这会将当前目录中所有 Python 文件的编译字节码放入其中__pycache__/,并显示任何编译器错误

如果你想知道那个__pycache__/文件夹中到底有什么,我为 EdmontonPy 做了一个演讲,你应该看看!

演讲地址:https://www.youtube.com/watch?v=5yqUTJuFuUk&t=7m11s

只有在 Python 被编译为字节码之后,解释器才会真正启动,我希望前面的练习已经证明 Python 确实可以在运行时之前报错

编译语言和解释语言是错误的二分法

每当一种编程语言被归类为【编译】或【解释】语言时,我都会感到很讨厌。一种语言本身不是编译或解释的

一种语言是编译还是解释(或两者兼而有之!)是一个实现细节

我不是唯一一个有这种想法的人。Laurie Tratt 有一篇精彩的文章,通过编写一个逐渐成为优化编译器的解释器来论证这一点

文章地址:https://tratt.net/laurie/blog/2023/compiled_and_interpreted_languages_two_ways_of_saying_tomato.html

还有一篇文章就是 Bob Nystrom 的 Crafting Interpreters。以下是第 2 章的一些引述:

编译器和解释器有什么区别? 事实证明,这就像问水果和蔬菜之间的区别一样。这似乎是一个二元的非此即彼的选择,但实际上“水果”是一个植物学术语,而“蔬菜”是烹饪学术语。 严格来说,一个并不意味着对另一个的否定。有些水果不是蔬菜(苹果),有些蔬菜不是水果(胡萝卜),但也有既是水果又是蔬菜的可食用植物,如西红柿

当你使用 CPython 来运行 Python 程序时,源码会被解析并转换成内部字节码格式,然后在虚拟机中执行

从用户的角度来看,这显然是一个解释器(因为它们从源码运行程序),但如果你仔细观察 CPython(Python 也可译作蟒蛇)的鳞状表皮(scaly skin),你会发现它肯定在进行编译

答案是:CPython 是一个解释器,它有一个编译器

那么为什么这很重要呢?为什么在【编译】和【解释】语言之间做出严格的区分会适得其反?

【编译】与【解释】限制了我们认为编程语言的可能性

编程语言不必由它是编译还是解释来定义的!以这种僵化的方式思考限制了我们认为给定的编程语言可以做的事情

例如,JavaScript 通常被归入“解释型语言”类别。但有一段时间,在 Google Chrome 中运行的 JavaScript 永远不会被解释——相反,JavaScript 被直接编译为机器代码!因此,JavaScript 可以跟上 C++ 的步伐

出于这个原因,我真的厌倦了那些说解释型语言必然慢的论点——性能是多方面的,并且不仅仅取决于"默认"编程语言的实现

JavaScript 现在很快了、Ruby 现在很快了、Lua 已经快了一段时间了

那对于通常被标记为编译型语言的编程语言呢?(例如 C)你是不会去想着解释 C 语言程序的

语言之间真正的区别

语言之间真正的区别:【静态】还是【动态】

我们应该教给学生的真正区别是语言特性的区别,前者可以静态地确定,即只盯着代码而不运行代码,后者只能在运行时动态地知道

需要注意的是,我说的是【语言特性】而不是【语言】,每种编程语言都选择自己的一组属性,这些属性可以静态地或动态地确定,并结合在一起,这使得语言更“动态”或更“静态”

静态与动态是一个范围,Python 位于范围中更动态的一端。像 Java 这样的语言比 Python 有更多的静态特性,但即使是 Java 也包括反射之类的东西,这无疑是一种动态特性

我发现动态与静态经常被混为一谈,编译与解释混为一谈,这是可以理解的

因为通常使用解释器的语言具有更多的动态特性,如 Python、Ruby 和 JavaScript

具有更多静态特性的语言往往在没有解释器的情况下实现,例如 C++ 和 Rust

然后是介于两者之间的 Java

Python 中的静态类型注释已经逐渐(呵呵)在代码库中得到采用,其中一个期望是:由于更多静态的东西,这可以解锁 Python 代码中的性能优势

不幸的是,事实证明,Python 中的类型(是的,只是一般类型,考虑元类)和注释本身都是Python 的动态特性,这使得静态类型不是大伙所期望的性能优势

最后总结一下:

  • CPython 是一个解释器,它有一个编译器(或者说 Python 既是解释型语言,也是编译型语言)

  • Python 是编译的还是解释的并不重要。重要的是,相对于那些具有更多静态属性(在编译或解释阶段可以在运行前确定的属性)的编程语言,Python 中可以在运行前确定的属性相对较少,这意味着在 Python 中,许多属性是在运行时动态确定的,而不是在编译或解释时静态确定的

  • 由于 Python 具有较少的静态属性,这意味着在运行时,某些错误可能只能在运行时才会显现,而不是在编译或解释时就能被发现

  • 这是真正重要的区别,这是一个比【编译】和【解释】更细致、更微妙的区别。出于这个原因,我认为强调特定的静态和动态特性是很重要的,而不是一昧的局限于“解释型”和“编译型”语言之间的繁琐的区别。

​欢迎加入电报频道:

https://t.me/pythontrendingweekly

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
刚向美国否认完“强迫劳动”,宁德时代就被曝要求员工896,奋斗100天

刚向美国否认完“强迫劳动”,宁德时代就被曝要求员工896,奋斗100天

小萝卜丝
2024-06-16 08:32:04
乌克兰和平峰会、美俄核潜艇“对视”,都是俄乌冲突背景音乐!

乌克兰和平峰会、美俄核潜艇“对视”,都是俄乌冲突背景音乐!

新民周刊
2024-06-16 09:09:58
乌情报总局局长:俄最新型S-500防空系统部分部件已运抵克里米亚

乌情报总局局长:俄最新型S-500防空系统部分部件已运抵克里米亚

红星新闻
2024-06-15 20:16:15
网传南方医科大学老师因救人导致上课迟到,被留学生举报后,遭学校罚款+通报批评

网传南方医科大学老师因救人导致上课迟到,被留学生举报后,遭学校罚款+通报批评

可达鸭面面观
2024-06-16 10:28:07
国民党高层大陆讲话内容惊人,民进党彻底慌了,台陆委会紧急回应

国民党高层大陆讲话内容惊人,民进党彻底慌了,台陆委会紧急回应

说天说地说实事
2024-06-16 12:15:12
我是刘奔,第13名:愿她在服装设计和数学方面都可以继续保持这份热爱

我是刘奔,第13名:愿她在服装设计和数学方面都可以继续保持这份热爱

新民周刊
2024-06-16 13:03:32
苏格兰男球迷裙下没穿内裤直接漏出私处,西班牙记者被雷到

苏格兰男球迷裙下没穿内裤直接漏出私处,西班牙记者被雷到

直播吧
2024-06-15 18:41:04
智利正式申请加入《区域全面经济伙伴关系协定》

智利正式申请加入《区域全面经济伙伴关系协定》

界面新闻
2024-06-16 07:10:30
再次爆料!美国媒体:3名中国游泳选手呈阳性,2人是东京奥运冠军

再次爆料!美国媒体:3名中国游泳选手呈阳性,2人是东京奥运冠军

体坛知识分子
2024-06-16 06:20:02
获全场最佳!西班牙中场技术出众却毫无名气,主帅:名字耽误了他

获全场最佳!西班牙中场技术出众却毫无名气,主帅:名字耽误了他

星耀国际足坛
2024-06-16 11:36:04
辛酸!队友绝杀之后拜合拉木迅速捡球 这次他没敢庆祝

辛酸!队友绝杀之后拜合拉木迅速捡球 这次他没敢庆祝

球事百科吖
2024-06-16 12:02:51
闹大了!陈芋汐代言李宁,造型酷似日本风,网友直接炸锅了

闹大了!陈芋汐代言李宁,造型酷似日本风,网友直接炸锅了

吃鱼思故渊
2024-06-15 21:49:52
大消息,沙特终止与美石油美元协议!国际油价创4月以来最大周涨幅

大消息,沙特终止与美石油美元协议!国际油价创4月以来最大周涨幅

金融界
2024-06-16 08:00:08
中国航天员打破纪录!在太空行走8.5小时,这意味着什么?

中国航天员打破纪录!在太空行走8.5小时,这意味着什么?

奇点使者
2024-06-15 13:35:03
上海电影节众男星状态:45岁邓超显沧桑,李治廷寸头造型阳刚帅气

上海电影节众男星状态:45岁邓超显沧桑,李治廷寸头造型阳刚帅气

扒虾侃娱
2024-06-15 21:23:43
倒查30年后补税是个危险信号

倒查30年后补税是个危险信号

深度财线
2024-06-15 22:03:47
高中生模仿宋徽宗瘦金体,被老师打0分,评语:不要挑战考试底线

高中生模仿宋徽宗瘦金体,被老师打0分,评语:不要挑战考试底线

熙熙说教
2024-06-16 12:08:10
何谓“法律面前人人平等”?美国司法界判拜登儿子和特朗普都有罪

何谓“法律面前人人平等”?美国司法界判拜登儿子和特朗普都有罪

爆角追踪
2024-06-15 22:57:15
离谱!刚出的联名杯子立马就被黄牛上架,看到价格沉默了

离谱!刚出的联名杯子立马就被黄牛上架,看到价格沉默了

苹果牛看游戏
2024-06-16 10:45:36
“欧洲杯”火到国内:电竞酒店预订热度涨三倍,酒吧零点场约满

“欧洲杯”火到国内:电竞酒店预订热度涨三倍,酒吧零点场约满

澎湃新闻
2024-06-16 09:30:26
2024-06-16 15:24:49
Python猫
Python猫
人生苦短,我用Python。博客:https://pythoncat.top
633文章数 8097关注度
往期回顾 全部

科技要闻

iPhone 16会杀死大模型APP吗?

头条要闻

牛弹琴:梅洛尼和马克龙吵了一架 晚宴上眼神可"杀人"

头条要闻

牛弹琴:梅洛尼和马克龙吵了一架 晚宴上眼神可"杀人"

体育要闻

没人永远年轻 但青春如此无敌还是离谱了些

娱乐要闻

上影节红毯:倪妮好松弛,娜扎吸睛

财经要闻

打断妻子多根肋骨 上市公司创始人被公诉

汽车要闻

售17.68万-21.68万元 极狐阿尔法S5正式上市

态度原创

手机
教育
旅游
亲子
军事航空

手机要闻

父亲节送礼不纠结,华为畅享 70系列值得重点考虑,实用性拉满

教育要闻

无一入围决赛 据经济观察报 依据赛事官方统计,参与竞赛的AI队伍的平均分为18分,已赶上人类选手的平...

旅游要闻

@毕业生,江苏这些景区可享免票或优惠

亲子要闻

幼儿园给孩子办“集体婚礼”,为何遭非议   新京报快评

军事要闻

以军宣布在加沙南部实行"战术暂停"

无障碍浏览 进入关怀版