在编程时,我们经常要作条件判断,并根据条件的结果选择执行不同的语句块。在许多编程语言中,最常见的写法是三元运算符,但是,Python 并不支持三元运算符,无独有偶,两个最热门的新兴语言 Go 和 Rust 也不支持!

为什么 Python 不支持三元运算符呢?本文将主要分析 Python 在设计条件选择语法时的过程,科普为什么它会采用现今的与众不同的实现方案,同时,我们也将考察为什么其它语言也要抛弃传统的三元运算符。

在开篇之前,我再声明一下:就像“Python为什么”系列的大部分文章一样,本文关注的仅是一个很小的语法点,但它并不是“茴香豆有几种写法”那种毫无意义的话题。因为,细微之处见真功夫,深入研究语言设计背后的原因、历史和哲学,可以让我们在编程时有更加清晰和自由的思维。

什么是三元运算符?

三元运算符通常指的是“?:”,其语法形式为:condition ? expression1 : expression2,如果 condition 为真,则取 expression1,若不为真,则取 expression2。

语法简化形式“a ? b : c”,可以读成“如果 a 条件成立,则为 b,否则为 c”。

三元运算符是对普通一重 if-else 结构的简化,常用于在一条语句中同时实现条件判断和取值操作。

// 常规 if-else
if (a > b) {
result = x;
} else {
result = y;
} // 简化后的写法
result = a > b ? x : y;

采用了这种语法设计的编程语言有很多,比如 C、C#、C++、Java、JavaScript、PHP、Perl、Ruby、Swift 等等。毫无争议,它就是编程语言界的主流设计方案(至今仍是)。

这种语法非常简洁高效,代码的可读性也很强(如果你不是第一次接触的话),深得很多人的喜欢。

但是,它并非毫无缺点。Python 是这种语法设计的最著名的挑战者,接下来,我们将看看为什么 Python 要另辟蹊径。

Python 社区的投票

Python 发布于 1991 年,但在接下来的 15 年里,除了 if-else 语法外,它并不支持三元运算符和其它条件表达式。而且,在 2006 年引入条件表达式前,社区对此进行了漫长而曲折的争论,可以说这是一个设计得很艰难的语法了。

最初,由于时常有人请求添加 if-then-else(三元)表达式,因此在 2003 年 2 月,PEP 308 – Conditional Expressions 被提了出来,目的是让社区选出一个让多数人支持的方案。

很快,除了少部分人希望啥也不做外,社区里出现了好几种方案:

(1)使用标点符号构建的三元运算符

即常规的三元运算符,跟前文介绍的语法一样:

<condition> ? <expression1> : <expression2>

这个方案的呼声挺高,有开发者甚至已提交了实现代码。但是,Guido 给出了两个反对的理由:冒号在 Python 中已经有许多用途(即使它实际上不会产生歧义,因为问号需要匹配冒号);对于不习惯 C 衍生语言的人来说,理解起来很困难。

(2)使用现有和新的关键字构建

引入新的“then”关键字,结合现有的“else”关键字:

<condition> then <expression1> else <expression2>

它的优点是简单明了、不需要括号、不改变现有关键字的语义,不大可能与语句混淆,而且不需要重载冒号。缺点是引入新关键字的实现成本较高。

(3)其它思路

跟上一种方案的思路相似,但没有上述两类方案的支持度高。

(if <condition>: <expression1> else: <expression2>)
<condition> and <expression1> else <expression2>
<expression1> if <condition> else <expression2>
cond(<condition>, <expression1>, <expression2>)

值得一提的是(if <condition>: <expression1> else: <expression2>) ,它是常规 if-else 语法的扁平化,容易理解,但缺点是需要使用圆括号,容易跟生成器表达式混淆,而且需要解释器对冒号做特殊化处理。

另外值得一提的是<expression1> if <condition> else <expression2>,它是 PEP-308 最早版本的推荐方案,但是这种不将条件放在首位的风格让一些人感觉不舒服,而且,当“expression1”很长的时候,很容易就忽略掉它的条件。

当时参与投票的全部设计方案:

总体上,开发者们希望引入某种形式的 if-then-else 表达式,但投票后却没有哪种方案能取得绝对的优势。概括起来,分歧的问题主要有:是否用标点符号、是否复用关键字、是否复用圆括号、是否引入新关键字、是否引入新语法……

由于得票太分散,因此,这个 PEP 在当时被拒绝了。PEP 中写道:“Python 的一个设计原则是在不确定采取哪条路线时,则保持现状。”

and-or 用于条件选择的问题

以上的投票事件发生在 2004 年 3 月,但是,在 PEP 被拒绝后,相关话题的讨论并未平息,因为大家总想找一种简洁的方式来替换“if-else“。

时间到了 2005 年 9 月,邮件组中有人提议在 Py3.0 中变更"and"与"or"操作符的逻辑,提议将"and" 和 "or" 运算符简化成始终返回布尔值,而不是返回最后一个被求值的参数。

之所以发起这个提议,原因是他使用了<condition> and <expression1> or <expression2>的方式来实现条件判断与选择。但是这种写法在 Python 中的行为跟有些语言并不一样,使用不严谨的话,可能会酿成 Bug!

看看下面的两个例子,你觉得它们会得到什么结果呢?

a = True and True or "Python猫"

b = True and False or "Python猫"

对于<condition> and <expression1> or <expression2> ,若 condition 为假,则会直接对 expression2 求值并返回结果;若 condition 为真,则先对 expression1 求值,若也为真,则不会继续对 expression2 求值,若 expression1 不为真,则对 expression2 求值。

因此,上述例子得到的 a 是“True”,而 b 会得到“Python猫”。

本系列的《Python 为什么能支持任意的真值判断? 》介绍过 Python 在真值判断的特殊之处,运用到以上结构中,将出现更不易察觉的问题。比如,该邮件的作者就是遇到了“expression1”为复数“0+4i”,这个数的真值判断为 False,因此导致最后返回的不是预期的“expression1”,而是“expression2”!

在没有更好的方案前,“and-or”是比较常见的条件选择写法,PEP-308 也提及了它,也指出了当“expression1”为假的情况,还认为这种方案是丑陋和令人费解的。

这封邮件再次引发了社区对条件选择语法的讨论,大佬们纷纷登场。

以我现在的视角分析,其实就是开发者们不满足于“if-else”的现状,但是当时流行的“and-or”写法并不够好,因此,大家期望 Python 设计出新的规范性语法,来解决这个痛点。

与众不同的条件表达式

在经过 10 天的邮件讨论后,Guido van Rossum 最终决定添加一个条件表达式,语法形式为X if C else Y 。因此,PEP-308 被重开和更新,并很快就在次年的 2.5 版本中实现了。

前文已提到过这个让一些人感觉不舒服的方案了,因为它没有将条件判断逻辑放在最前面。

那么,为什么最后的胜者会是它呢?这是不是最优的设计呢?

不可否认,起到决定性作用的原因是 Guido。由于社区在一年半前投票时没有形成多数意见,因此他行使 BDFL (终身仁慈独裁者)的决策权力,裁定出一个他认为是最佳的方案。

X if C else Y 非常易于理解,可读性高。它延续了“明确优于隐式”的风格,使用了直观口语化的“if-else”,而不是引入可能引起混淆的标点符号,就像 Python 选择“and”和“or”两个单词,而不是“&&”和“||”两个符号,它们有着异曲同工之妙。

虽然调整后的语法顺序让人不太习惯,但其实这样的实现却大有好处。首先,它只需复用“if-else”两个关键字,而不需要引入“then”、“when”和其它语法要素,也不像(if <condition>: <expression1> else: <expression2>) 那样的繁琐。

其次,为了验证X if C else Y 的有效性,Guido 排查了标准库中所有“and-or”组合的写法,发现那些C and X or Y 写法都可以被X if C else Y 替换掉。标准库的情况,证明了这新的语法是可行的。

最后,在 PEP-308 提及的原因外,我还想补充一点。据观察,我发现很多时候我们有一个已初始化的变量,然后需要在出现某个条件时,更新变量的值。在这种情况下,“else”部分可以被省略,非常便捷。

my_str = ""
# 中间存在其它代码逻辑
# 当 condition 为真时,变量会被重新赋值
my_str = "Python猫" if condition

回顾这段历史,我们可以梳理出一条线索:Python 没有设计三元运算符“?:”,主要是因为它不符合 Python 明确直观的设计风格。最后采用X if C else Y 这种设计,主要的意图其实是消除“and-or”写法的隐患,这种设计简明易读,而且还有<expression> if <condition> 简化写法的妙用。

总体而言,Python 设计者非常看重可读性与可维护性,不采用三元运算符而创造条件表达式语法,这是一个经过了开放讨论、谨慎评估与权衡取舍的结果。

Go、Rust 为什么不支持三元运算符?

考察完 Python 的设计原因后,我们再来考察“反派阵营”中两门最热门的语言。

首先是 Go 语言,官网的 FAQ 专门列出了一个问题:“Why does Go not have the ?: operator?”。

Go 语言不支持“?:”运算符,而是推荐使用原生的“if-else”写法。文档的解释很简短,只有一段话:

Go 语言没有 ?: 运算符,因为语言的设计者们经常看到它被用来创建难以理解的复杂表达式。虽然 if-else 形式比较长,但是它无疑更清晰易懂。一个语言只需要一个条件控制流结构。

接着是 Rust 语言,它的官方文档中似乎没有任何关于不支持三元运算符的解释。但在查阅资料后,我发现它也有一段特殊的故事,非常有意思:在 2011 年 6 月时,Rust 曾经引入过三元运算符(#565),然而半年后,设计者意识到这个特性是多余的,因此又把它移除了(#1698、#4632)!

为什么三元运算符在 Rust 是多余的呢?因为它的 if 语法并不像其它语言是“语句(statement)”,而是一个“表达式(expression)”,这意味着你可以直接将 if 表达式赋值给变量:

// 若条件为真,得到 5,否则 6
let number = if condition { 5 } else { 6 };

这种语法形式足够简单明了,不就是将大家都熟悉的“if-else”直接用于赋值么,太方便了,替换成三元运算符的话,确实有点画蛇添足之感。

另外,Rust 使用花括号划分代码块,因此上例的花括号内可以包含多条表达式,也支持换行,例如这个例子:

let x = 42;
let result = if x > 50 {
println!("x is greater than 50");
x * 2 // 这是一个表达式,将返回的值赋给 result
} else {
println!("x is less than or equal to 50");
x / 2 // 也是一个表达式,将返回的值赋给 result
};

这种用法,Python 是不可能做到的。最关键的区别在于,Rust 的 if 是表达式而不是语句。

这两个概念的区别是:

  • 表达式(expression)通常指的是由变量、常量、运算符等组成的一个可求值的代码片段,它的求值结果可以用到其它表达式或语句中。
  • 语句(statement)通常指的是完成某个任务的单个指令或一组指令,例如赋值语句、条件语句、循环语句等,它没有返回值(或者为空),不能用于赋值操作。

除了 Rust 外,还有一些编程语言中的 if 是表达式而不是语句,例如 Kotlin、Scala、F#、Swift,它们在理论上也不需要使用三元运算符。(题外话:Swift 是个例外,它也有三元运算符。Kotlin 有“?:”运算符,注意两个符号是连在一起的,val result = a ?: b 表示:如果 a 不为 null,则赋值给 result ;否则将 b 赋给 result)

由于有这种语言设计层面的区别,因此在面对“是否要支持三元运算符”这个问题时,Rust 和 Python/Go 的思考角度有着天然不同的起点。知道了这种区别后,我们对编程语言会有更明晰地认知。

回到本文的问题:为什么有些编程语言不采用主流的三元运算符语法呢?

不可否认,“?:”确实是一种简洁好用的设计,然而,标点符号的负面影响是过于抽象,可读性并不及“if-else”那样强。另外,不同语言的设计风格与使用习惯,也会导致不同的选择。

Python 在经过一番波折后,最后设计出了与众不同的条件表达式。Go 语言明确表示不支持三元运算符。Rust 先设计后舍去,主要的原因在于 if 表达式的语言基础。

考察完这三个热门语言后,我相信你已收获了一个满意的答案。如果是这样,请点赞支持一下本文吧!

最后,本文出自“Python为什么”系列,全部文章已归档在 Github 上,欢迎 star 和提 issue。

为什么 Python、Go 和 Rust 都不支持三元运算符?的更多相关文章

  1. Python学习手册之元组拆包、三元运算符和 else 语句深入

    在上一篇文章中,我们介绍了 Python 之禅. Python 编程规范和函数参数,现在我们介绍 Python 的元组拆包.三元运算符和对 Python 的 else 语句深入讲解.查看上一篇文章请点 ...

  2. Python基础-Python的三元运算符和lambda表达式

    1. Python的三元表达式: 现在大部分高级语言都支持 “?”这个三元运算符,它对应的表达式如下:condition ? value if true:value if else 但是 Python ...

  3. 4.1 python中调用rust程序

    概述 使用rust-cpython将rust程序做为python模块调用: 通常为了提高python的性能: 参考 https://github.com/dgrunwald/rust-cpython ...

  4. 我写的 Python 代码,同事都说好

    原文链接: 我写的 Python 代码,同事都说好 人生苦短,我用 Python. 程序员的追求就是不写代码,早日财务自由.不对,一不小心把实话说出来了,应该是将代码写得简洁,优雅. Python 程 ...

  5. 难道.NET Core到R2连中文编码都不支持吗?

    今天写了一个简单的.NET Core RC2控制台程序,发现中文显示一直是乱码.查看操作系统设置,没有问题:查看源文件编码,也没有问题:甚至查看了Console字符编码相关的注册表,依然没有发现问题. ...

  6. 对于python,一切事物都是对象,对象基于类创建

    新建列表.新建string字符串 li1 = [1, 2, 3, 4] li2 = list([1, 2, 3]) s1 = "abc" s2 = str("abc&qu ...

  7. 让VC2012生成的程序支持XP系统(QT的DLL都是支持XP的,只与EXE有关)

    如果用的编译器是VC2012以上,那么默认生成出的程序是不能运行在XP系统上的.所以需要修改链接参数 我们要做的是修改qmake.conf文件中的参数,文件路径根据开发环境不同而不同下面以5.1.1 ...

  8. python模块:网络协议和支持

    python模块:网络协议和支持 webbrowser 调用浏览器显示html文件 webbrowser.open('map.html') [webbrowser - Convenient Web-b ...

  9. 让VC2012生成的程序支持XP系统(修改mkspecs\win32-msvc2012\qmake.conf,QT的DLL都是支持XP的,只与EXE有关)good

    如果用的编译器是VC2012以上,那么默认生成出的程序是不能运行在XP系统上的.所以需要修改链接参数 我们要做的是修改qmake.conf文件中的参数,文件路径根据开发环境不同而不同下面以5.1.1 ...

  10. 解决Python内CvCapture视频文件格式不支持问题

    解决Python内CvCapture视频文件格式不支持问题 在读取视频文件调用默认的摄像头cv.VideoCapture(0)会出现下面的视频格式问题 CvCapture_MSMF::initStre ...

随机推荐

  1. 20200926--图像旋转(奥赛一本通P96 9 多维数组)

    输入一个n行m列的黑白图像,将它顺时针旋转90度后输出. 输入:第1行包含两个整数n和m(1<=n<=100,1<=m<=100),表示图像包含像素点的行数和列数. 接下来n行 ...

  2. C# Async / Await State Machine

    The async/await keywords in C# are very much syntactical sugar that the compiler will use to generat ...

  3. 初识 Linux Shell

    初识 Linux Shell 本书学习的第一步,就是要找到 Linux 终端的所在位置.目前较常见的图形化终端有 Konsole.Gnome terminal.xterm 等几种.一般安装后在各个发行 ...

  4. java计时器Timer

    基于java的计时器,定时发送消息 package timer; import java.lang.reflect.Array; import java.text.ParseException; im ...

  5. mkcert 生成本地SSL证书 IIS 安装

    下载mkcert https://github.com/FiloSottile/mkcert/releases/latest 管理员身份 cmd 命令目录下 mkcert-v1.4.4-windows ...

  6. Vivado中综合,实现,编程和调试工程可能会出现的问题及解决方案

    Xilinx公司的IDE(集成开发环境) Vivado用处广泛,学会使用Vivado对FPGA的学习至关重要,这里以PRX100-D开发板为例,对Vivado的学习使用进行探讨.本文将会持续更新,列出 ...

  7. 接口文档API、剖析http协议, 原生http请求函数

    http协议:超广本传输协议 特点: 短连接 请求完成后就断开 无状态  对于事务处理无记忆能力 媒体独立 客户端要指定适合的传输内容类型,如json http  是建立在tcp/ip协议之上的应用层 ...

  8. AD使用积累 - 相同网络的覆铜和走线无法自动连接问题

    像下图中这样,铜皮和走线是同一个网络,却没有连在一起. 解决方法: 选中目标铜皮,在在Properties中的Fill Mode中找到这个部分,先择Pour Over All Same Net Obj ...

  9. SQL Server之Cross apply

    1 --这样是不行的 2 select sys.dm_exec_sql_text(most_recent_sql_handle) from sys.dm_exec_connections 3 4 -- ...

  10. MySQL数据库性能优化的八种方式

    1.选取最适用的字段属性 MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快.因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽 ...