第 341 期·中文精读

吉多·范罗苏姆:Python 与编程的未来

Guido van Rossum: Python and the Future of Programming

Python 之父回顾这门语言的设计初衷,并讨论编程语言与开发者工具的未来走向。

官方音频00:00 / --:--
中文精读由 AI 基于原始访谈改写,非官方翻译,可能存在偏差或错误;表述以原音频与官方文稿为准。说明

Python 3.11 发布前夕,CPython 团队给出的承诺是新版本比上一版快 10% 到 60%。一门三十多年前动笔的语言,靠改自己的底层实现拿到这个量级的提速,这份成绩单放在任何工程领域都算漂亮。

Lex Fridman 问吉多·范罗苏姆(Guido van Rossum):这是怎么做到的。

他没接这个问题,先纠正了问题里的一个词。

"CPython 是最后一个还站着的 Python 实现,也是历史上第一个,我三十多年前亲手写的那个。"(吉多·范罗苏姆)

"最后一个还站着的",这句话说得很轻。一门编程语言通常可以有多个实现,Jython 跑在 Java 虚拟机上,IronPython 跑在 .NET 上,PyPy 用 Python 自己写自己。这些名字前面都得挂个前缀,因为不加前缀就不知道你在说谁。只有 CPython 不需要,它就是 Python 本身。

名字里的 C 是它用 C 语言写成。一门语言的默认实现,是用另一门语言写的程序。这件事在外行眼里很反常,在程序员眼里是常识,而这期节目的听众里,这两种人都有。

一条船上坐着两个人,其中一个只会钓鱼

Lex 把听众劈成了两类。一类是在船上钓鱼、从没听说过编程的人,另一类是写遍 C、C++、Python、Rust、Java,能把编程语言发展史倒背如流的硅谷顶级工程师。一条船,两个人,中间隔着整个计算机科学。

吉多选了先让钓鱼那位满意。

"在海上,他显然是更有用的那个人。你得让他高兴。"(吉多·范罗苏姆)

怎么跟他讲?他手里一定有个手机,所以他对手机里发生的事情大概抱有怀疑。但他至少听说过手机里有一块小小的计算机,而编程语言就是告诉这块计算机该干什么的代码。再往下就别讲了:零和一,汇编,寄存器。讲这些等于在讨论人类语言的时候去研究声道、舌位和气流。语言学讨论"我爱你"这三个字的意思,不讨论你的舌尖抵在上齿背还是下齿背。

Lex 在这里插了一段自己的观察。他说中文母语者讲英语时常常分不清 L 和 R,他有个从没找语言学家验证过的猜想:汉语里这两个音本来就没有对立,而不同地区的人在发那个中间音时,有的听起来像 L,有的听起来像 R。吉多顺着往下接:斯拉夫语系里有个 j 音,英语母语者发起来很别扭。两个人聊了几句发音,然后一起把这个话题放下了。

放下得对。再往下就是舌头怎么摆了,而舌头怎么摆,和编程语言是什么,中间隔着好几层抽象。

编程语言是一份菜谱,但要喂给两个读者

吉多给出的定义是:编程语言就是一份菜谱,一行行告诉你该怎么做出某样东西。

买一条面包,切片,取两片,一片抹芥末,一片抹果酱,然后放肉,再放奶酪。你要把这件事说得足够清楚,清楚到没有任何一种理解方式会出错。

Lex 说他见过科学老师玩这个游戏:让学生写下做三明治的步骤,老师严格照着做,做到学生抓狂为止。学生的意思是"拿两片面包",老师就真的把整袋面包都拿过来。

这个游戏暴露的正是自然语言和编程语言的根本差别。在人类口语里,歧义是特性,不是缺陷。它是人与人之间那场舞蹈的一部分,你说一半,我猜一半,猜错了笑一下,再补一句。律师喜欢歧义,因为歧义就是诉讼费。日常对话里歧义也会带来麻烦,但人们绕着走,靠上下文把它填平。

对着一个你爱的人说"我爱你",对方不会试着编译这句话然后报错:请定义"love"。

"不会。不过我猜我太太和我儿子听到这三个字,理解会非常不一样。"(吉多·范罗苏姆)

同样三个词,同一个句子,两个听众,两种含义。靠的还是上下文。换成律师,他会追问你二十个问题。

编程语言干的事,就是把这个上下文抽掉,把一份菜谱写成不管谁来读都只能读出一种意思的东西。

《Python 之禅》里那句"可读性很重要"是说给谁听的

Lex 提到那份被无数 Python 开发者当口诀背的东西,《Python 之禅》。里面有一句:可读性很重要(Readability counts)。

这句话初看很怪。代码是给机器看的,机器不在乎你缩进几个空格,你在中间插一行脏话它照样跑。可读性是给谁的?

吉多的回答把整件事翻了个面。软件开发从四十年代末开始,就一直是件社交活动。那个坐在实验室里独自写出天才代码的疯狂科学家,其实并不存在。就算真的存在,他打字的速度也快不到哪去,等到他写完最后一行,前面那几十行写了什么,他自己也记不清了。

所以写代码的时候,你同时在跟两个对象说话。

一本菜谱只有一个读者。作者写一次,印十万份,十万人照着做樱桃派。作者的全部目标是让厨房里那位业余厨师看懂。软件这个行当完全不同。软件太复杂,复杂到你不可能一次写对,所以必然有调试,必然有修改,必然有别人接手,也必然有一年之后的你自己回来看这段代码。

两个读者,两套要求,还经常打架。这就是为什么"代码风格"这种东西在别的行业听起来像个笑话,在软件行业却值得写进语言的核心文档。

调试是三个完全不同的东西,被同一个词装在一起

Lex 让吉多给调试下个定义。那个回答被切成了三层。

最浅一层是蠢错误。少打一个字母,括号没配对,路径写错。

中间一层是逻辑错误。程序完全按你写的跑了,跑出来的东西是错的,因为你想错了。

最深一层是这玩意压根没用。代码没错,逻辑没错,跑得又稳又快,只是它解决的问题不该被解决,或者解决的方式让产品变得没法用。

还有第四层,吉多专门拎出来说:程序 99% 的时间表现完美,然后在某个边缘情况上突然发疯。这是今天几乎所有软件的真实状态。好软件是这样,差软件那个 99% 会掉到 90% 甚至更低。

这句话往下推,得出的结论不太舒服。你今天用的每一个软件,包括那些支撑银行、医院和电网的系统,都有若干条路径是你从来没走过、也永远不会被走到的。软件工程的成熟,相当程度上是学会了在这些路径上放护栏,而不是假装它们不存在。

四个空格是怎么来的

看 Python 代码的第一眼,看到的往往是形状。左边那道阶梯,一层往里缩一点,像一份排版讲究的商务文档。

吉多的解释很朴素。任何文本的可读性都靠空间,把一份菜谱的所有项目符号删掉,句子揉成一整块,你在厨房里连"哪些是原料、哪些是步骤、这一步到哪结束"都分不出来。缩进就是给代码造出这种可见的分组:一段文字里套一段更小的文字,再套一段更小的。

问题在于缩进多少。缩进太深,三层之后屏幕上就没地方放字了。缩进太浅,一眼看不出这是第一层还是第二层。

答案是四。理由是古代排版惯例,加上八十年代那批显示器的典型宽度,两个约束一夹,四个空格落在中间。这是个妥协,谈不上定理。

Google 的 Python 代码风格指南用的是两个空格,据说 Google 的 C++ 代码也是。你不习惯的话,读起来会有点吃力,因为这种语言里,高层结构完全由缩进决定,缩进浅了,骨架就模糊了。经典 Unix 那边更夸张,一个 tab 八格,吉多觉得那看着别扭,三层之后就贴到屏幕右边了。

Python 的特殊之处在于,缩进在这里属于语法的一部分,风格建议这个说法太轻了。别的语言里,C、Rust、C++、Java,你把代码写成一整行,编译器照样给你编出来,只是同事会想打你。所以现实里 IDE 会自动帮你缩进,缩错了还会用红波浪线吼你。人类用工具把一条软规范硬成了事实标准,Python 只是把这条已经存在的规矩写进了语言定义。

花括号本来有可能出现在 Python 里

Lex 问了个历史问题:有没有那么一个平行宇宙,Python 是用花括号分块的?

"绝对有。"

吉多说,很长一段时间里他都确信缩进更好。就算把上下文全部抽掉,单看这个设计,他还是认为缩进更好,因为它减少了视觉噪音。

可惜上下文几乎就是一切。而编程这件事的上下文是:大多数程序员都熟悉好几门语言,哪怕只精通一两门。除了 Python,可能再加上 Fortran,其他所有主流语言,Java、Rust、C++、JavaScript、TypeScript、Perl,全用花括号标记代码块。Python 是那个异类。

异类是有代价的。一个从 Java 转过来的人,第一天写 Python 会觉得少了点什么,像走路时突然发现自己一直在无意识地甩手。

那为什么不改?

"Python 里没有这个可能性。花括号在 Python 里已经用来干别的了,主要是字典。真要想换,会惹出一堆麻烦。"(吉多·范罗苏姆)

他还补了一句更根本的:你不能重新定义 C 让它是用缩进的,哪怕你打心底认为缩进更好。一门语言里,这种级别的东西改不动。比这细小得多的细节,想达成一致都已经够难了。Python 历史上真为 tab 和空格打过一场大仗,为四个空格还是两个还是八个吵过,最后的产物是一份推荐标准,外加一堆给不听话的人准备的选项。

时间旅行回去,他还是会选缩进

Lex 不放过他,把假设推到底:如果能穿回三十多年前,兼容性完全不成问题,Python 从头再来一遍,你还会选缩进吗?

会。理由有三条。

第一,它省下一对括号。而这世界上配对的东西永远不够用,省下来的括号可以拿去干别的(在 Python 里,它们后来确实去干别的了)。

第二,对完全没学过编程的人更友好。一门十三周的入门课,要把算法、数据结构、语法、变量、循环、函数、递归、类、表达式、运算符全塞进去。能少讲一个概念,就少一个让人分心的东西。分号是教学里最经典的麻烦:编译器每次都抱怨我分号放错了地方,或者我又忘了写分号。Python 没有分号,你忘不了它,也不会把它放到不该放的位置,因为你从一开始就没学过它。

第三,也是吉多自己补的反面:强制严格,本身就是在教东西。它告诉新手,编程是一件在意细节的事,你不能像写英语作文那样随便写。当然,新手要操心的细节还有很多,这条信息总能传达到。

美元符号是一块内存不够用的化石

Lex 换了个靶子:PHP 里变量前面那个美元符号。他说这玩意在他的理解里完全讲不通,而且打字累,还特别容易错。

吉多给了一段考古式的回答,讲的是 Unix shell 的来历。

最早的 shell 根本没有"给脚本传参数"这个概念。一个脚本就是几行文本,每行是一条命令,一个非常原始的命令处理器读它,把行首那个词当作程序名,剩下的一整行当作文本丢给程序自己去解析成参数。

后来人们想要变量了,尤其是想要参数。麻烦来了。你写下 program x y,这在原来的语义里已经有确定含义:x 是一个文件名。可你想说的是"x 是一个变量,它的值才是文件名"。这两件事必须能区分开。

而在五六十年代,写这个脚本解释器的程序本身是个极小的程序,得塞进很小的一块内存。最省事的做法是:逐字符扫描,看到某个特殊符号就跳到另一段代码,把后面一直到空格的东西读出来当变量名。选哪个符号?美元符号便宜、显眼、不容易和正常文本撞车。

它同时也是给人类读者看的。二十行之后你看到 xyz 前面挂着一个美元符号,立刻知道 xyz 是脚本的参数之一,硬盘上某个文件名这个身份被排除掉了。

PHP、Perl 都继承了这条血脉。IBM 更早的作业控制语言里也有类似的东西。这些设计活到了今天,活在支撑整个互联网后端的代码里。Wikipedia 里就有一大堆 PHP。

"这就像生物系统。DNA 的内部机制已经稳定了几亿年,里面全是各种古怪的遗留物。如果你从零设计一个能自我复制的分子系统,绝不会那么干。"(吉多·范罗苏姆)

Lex 顺着这个比喻往下说:生物系统有很多奇怪的冗余,会以奇怪的方式出错,但在有机体这个层面上依然有韧性。代码没有这种韧性。

吉多不同意。他说你会惊讶于现代代码有多强的韧性。哪怕是测试得很充分、实际运行良好的代码,里面也有很多地方是坏的,只是错误被一层层机制吸收掉了,包括最后那一层:用户。用户被告知重启电脑,重启一下,问题就消失了。温和一点的版本是刷新页面,所有人遇到怪事都会下意识刷两三次,然后才承认这里真有问题。

Lex 说这个习惯得改,因为刷第二次通常只是把灯又关上了。吉多说,那就刷三次,这才是正确的教训。

一千行代码里到底有几个 bug

Lex 抛出一个想法:如果把运行核武器的代码、运行 Twitter、Facebook、Dropbox、Google、Windows 的代码,按每千行的 bug 数排成一张表,人们会不会被吓到。

吉多说这取决于你读没读过这个领域的研究。他记得上世纪八九十年代有过一批研究,覆盖了各种软件、各种语言、各种公司、各种开发风格,结论相当稳定:在成熟的、被公认为质量很好的软件里,大约是每千行一个 bug。这已经是行业天花板。

Lex 随即摆出一份数据,来自一家做编程分析的公司,视角是开发者端:平均每个开发者每一千行代码写出 70 个 bug,其中 15 个会流到客户手里;修一个 bug 花的时间是写一行代码的 30 倍;开发者 75% 的时间花在调试上;美国一年在找 bug 和修 bug 上烧掉 1130 亿美元。

吉多的第一反应是承认自己错了一个数量级。

紧接着他做了那个关键的区分。每千行一个 bug,说的是经过长期测试、已经稳定下来的成熟软件。每千行 70 个,说的是你刚开始敲键盘的时候。这两个数字根本不测量同一件事。拿它们放在一张图里比较,就像拿新生儿的哭闹次数和成年人的说话次数做对比,然后得出人类越活越沉默的结论。

他也顺手把这页数据看穿了:这是一家卖工具的公司做的内容。页面底部必然挂着一个"联系我们"按钮,潜台词是,你只要掏出比一千一百三十亿少一点的钱,我们就帮你把这个坑填上。

Lex 提到 Stack Overflow 上有一份同主题的报告,他想打开对照一下,页面显示"网站维护中"。

"这挺讽刺的。"(吉多·范罗苏姆)

一个支撑了全世界最大问答社区的技术,在你想引用它的数据时告诉你它挂了。软件这东西的可靠性,大概就是这么回事。

你不可能在一年前就写好今天的发布计划

吉多接着解释那个 70 和 1 之间的落差从哪来。软件开发是极度迭代的。没有人真的会做一份"一年后发布什么"的完整计划,然后把细节全部敲定。因为那些细节本身拼起来就是一份程序,而只要它是一份程序,你的计划里就也会有 bug,也会有想错的地方。

所以你实际干的事是这样的:敲一堆代码,跑一下,看看它是不是你想的那个样子,不对就改,改完再跑。几十轮之后,剩下的那些错误,才轮得到"每千行一个"这个数字去描述。

Lex 在这里承认,那个 70 出现的时候他也吓了一跳。吉多的反应更平静:这个数字说明的是一件事情,代码在诞生阶段极其脏,脏到没法看,而工程的全部工作就是把这份脏慢慢洗干净。

他打字只用两根手指,而且不打算改

话题拐到了打字上。吉多说自己是个很糟的打字员,十个手指都在用,但用得很差。他从没上过打字课,也从没专门去纠正这件事。第一次认真学 QWERTY 键盘的布局,是大学里第一门编程课,那时候用的是打孔卡。他就用两根手指,一个键一个键地把代码戳出来。

Lex 说他有个观察方法:让任何一个程序员现场演示几行代码,你就盯着他按退格键的次数。有人会为了改行首的一个错字,往回退二十、三十、四十个字符,然后重新敲。熟练一点的会用方向键,或者动鼠标,但鼠标通常比方向键慢。很多人敲一个二十个字符长的单词,敲到一半发现开头错了,就把整个词退掉重来,有时候要重来三四次才对。

吉多在这里做了一个区分,这个区分挺重要。打错一个字然后立刻改掉,严格说不算 bug。但你确实为此浪费了时间,而且偶尔会有一个错字漏网。等你又往下敲了十行,你不知道那十行里哪一处有问题,只知道某处有问题。

这跟忘记初始化一个变量属于两个层面的问题。前者跑一遍就暴露了。后者在 Python 里也跑一遍就暴露了,现代编译器大多也能提前抓出来。真正麻烦的是第三种:变量你初始化了,名字也叫对了,但初始化的是另一个东西,或者你本意用的那个变量跟它重名了。这种东西编译器抓不到,测试也不一定覆盖得到,它会在某个深夜的线上环境里冒出来。

把退格键挪到拇指下面,是一个价值三十年的决定

Lex 顺势讲了自己为什么用 Kinesis 键盘:那种键盘把退格键放在拇指正下方。普通键盘的退格键在右手小指够得着的地方,每按一次你的整只手都要往外撑一下。很多人一辈子都在承受这个设计带来的疼痛,只是没意识到。

吉多问他,如果你在别人那儿,手边只有标准键盘怎么办。

Lex 的回答很实在:你可以一辈子随身带着键盘。他在飞机上要写代码或者大量打字的时候,会把笔记本摊开,再从包里掏出一把 Kinesis 摆在旁边。

"我已经接受了自己是个怪人这件事。"(吉多·范罗苏姆)

这句话往下接的是一段更普遍的经验:很多人都有自己的一套舒适区配置,某些特定的设置能把生产力拉满。有人看电影要盖一条特定的毯子,他要的是那把键盘。它带他进入一个能专注的状态。

然后他补了一句:他大部分时候都在努力用最先进的 IDE,但真正让他坐得住的,还是 Emacs。

这才是这期节目里最有共鸣的一段,因为它咬住了一个所有技术人都要反复面对的问题:该多紧地抱着顺手的工具,又该花多少精力去学新的。

社区一年一换的兴奋,多数是噪音

Lex 说,社区给你的信号是嘈杂的。每年都有一批人为了新工具兴奋,而你必须做一个判断:这些东西是在定义新一代的工作方式,还是会改变编程这件事的形态,或者只是一阵风,吹完就散。

前端尤其明显。JavaScript 框架、构建工具、状态管理方案,一波接一波地来,一波接一波地走。

他举了自己的例子。Flash 里的 ActionScript,他认真学过,学怎么在里面写程序,学怎么在里面做图形动画。Java applet 也是,他当年写了相当多的 applet,真心觉得这可能是 Web 的未来。

结果不是。

吉多的回应值得逐句读。他没有安慰说"你那些时间没白费",也没有附和说"那都是垃圾"。他说的是:这类技术最终都会死掉,但它们最早引入或者最早让大众接触到的那批概念,会被留下来。

Java applet 早就没了,可"页面里包含一小段代码,直接响应你的动作"这个念头,按下按钮、点开链接、鼠标悬停,一点没退场。Flash 当年做那些动画做得极其痛苦,后来被 JavaScript 的同类方案取代,那确实是更好的做法,但动画本身还在,只是换了个载体。

"技术演化经常是这样的:那个最终被扔掉的阶段,恰恰是让你能起步的东西。没有螺旋桨飞机,就不会有喷气式飞机。"(吉多·范罗苏姆)

Lex 在这里插了一句反驳,而且插得对。从功能上看是这样,从用户角度看也是这样,可从写代码的那个人角度看呢?那些泡在 ActionScript 里的时间,那些熬在 Java applet 调试上的夜晚,怎么算?

他没能把这个问题问完。文字稿到这里断了。

什么能活三十年,什么活不过五年

不过这条线其实已经被前面的内容回答了,只是答案散在各处,得自己拼。

看吉多怎么谈 Python 的缩进。他说这件事在 Python 里已经没有重新讨论的余地,因为花括号已经在语言里干了别的活,动这一个决定会连带扯出一堆东西。他还说,哪怕你认定缩进更好,你也不能回头去改 C。一门语言活到一定年头,它最重要的属性就从最优变成了稳定。稳定意味着已经写下的几亿行代码明天还能跑,意味着所有人脑子里那套肌肉记忆还成立。

再看那笔每千行一个 bug 的账。这个数字之所以能压到一,靠的是时间。是无数次上线、无数次线上事故、无数次把边界条件一条条补上。一个项目真正的资产,往往藏在它踩过的、并且被修掉的那些坑里,漂亮的设计图纸反倒排在后面。

Flash 死在第五年,Python 活到第三十年,两者的差别在语法优雅程度之外。关键变量是它有没有长出一整套让几百万人协作的基础设施:包的生态、标准库、风格共识、向上兼容的承诺。语法可以被抄走,这套东西抄不走。

所以回头看那些两年就没人提的框架,判断标准其实很朴素:它只是把某件事做得更方便,还是引入了一个会一直留下来的概念?前者是工具,后者是思想。工具会被下一个工具换掉,思想会渗进下一代工具里,换个名字重新出现。

吉多在访谈里反复回到同一个词:上下文。缩进好不好要看上下文,因为大家都在用花括号。美元符号难用还得留着,因为上下文是五十年前那台内存不够的机器。一个技术决策是对是错,很少能在它被做出来的那一刻判断,得等十年,看它长出什么。

写代码的人从这条线里能落下一个有点反直觉的结论。花在某个具体工具上的时间,可能真的会归零;花在理解它为什么这么设计上的时间,基本不会白费。ActionScript 的三个晚上,如果只学了语法,那就是三个晚上;如果顺手想明白了为什么在浏览器里跑动画要这么费劲,那笔账十年后还在给你分红。

Emacs 也是一样。它四十多年没死,键位设置合理这个理由站不住脚,恰恰相反,那些键位是当年那台机器的键盘布局留下的化石。它活着是因为它允许使用者把编辑器改造成自己想要的东西,这个念头活得比它自己久,今天所有现代编辑器都在继承这条思路。

三十多年前,吉多在一个圣诞节前后开始写一个解释器,当时的动机只是想做点事情打发时间。他没打算设计一门能拿奖的语言,他想设计一个自己愿意天天读的语言,然后坚持了三十年不改主意。这个决定后来变成了几百万人每天上班要用的东西。

这件事里可复制的部分不多,但有一点:他选的那条路,缩进代替花括号,可读性大于简短,从头到尾没变过。中间被质疑过,被打过仗,被 Google 的风格指南用两个空格顶回来过,他都认了,但主干没动。

一份菜谱要喂给两个读者,一个是机器,一个是人。三十年来所有成功的语言都在回答同一个问题:当两者打架的时候,你站哪边。吉多站了人这边,代价是 Python 在性能上一直被人嘲笑。3.11 那个 10% 到 60% 的提升,是他们在不改语言设计的前提下,回头去改底层实现拿到的。

不推翻自己,只是把地基挖深一点。长跑选手和短跑选手最大的区别,大概就在这里。

  • 看到"每千行 70 个 bug"这类数字,先问它测的是哪个阶段。开发中的代码和运行了五年的代码,差着一个数量级,混在一起比会得出完全错误的结论。
  • 隔一段时间去读自己一年前写的代码。如果读得毫不费力,说明对方水平和你一样且思路一致;如果读得很痛苦,那些痛苦的地方就是你该改的写法。
  • 评估一门新技术,别只看它让你写起来多爽,看它引入了哪个概念。概念会留下,语法会被下一个人推翻。
  • 缩进、分号、命名风格这类争论,交给自动格式化工具,别在会议上花时间。人类的判断力应该花在算法选择和边界条件上。
  • 把"一次写对"这个念头从流程里删掉。既然成熟代码也有千分之一的错误率,那就在架构上预留返工的空间,别指望这次不出错。
  • 退格键的位置值得认真对待。如果你每天要按几百次,把它挪到拇指下面,这个改动的投入产出比比大多数效率工具都高。
  • 不要因为一个工具"过时了"就否定自己学它的那段时间。检查一下你从里面带走了哪个概念,那个才是真正的收获。
  • 选定一套自己坐得住的工具,别每年重装一遍环境。舒适区省下的注意力,可以全部投给真正难的问题。

本集资源与出处