乐趣区

关于python:为什么-PythonGo-和-Rust-都不支持三元运算符

在编程时,咱们常常要作条件判断,并依据条件的后果抉择执行不同的语句块。在许多编程语言中,最常见的写法是三元运算符,然而,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。

退出移动版