PEP 308 – 条件表达式
- 作者:
- Guido van Rossum, Raymond Hettinger
- 状态:
- 最终
- 类型:
- 标准轨迹
- 创建:
- 2003 年 2 月 7 日
- Python 版本:
- 2.5
- 历史记录:
- 2003 年 2 月 7 日,2003 年 2 月 11 日
添加条件表达式
在 2005 年 9 月 29 日,Guido 决定以 “X if C else Y” 的形式添加条件表达式。 [1]
推动用例是使用 “and” 和 “or” 达到相同效果的错误尝试的普遍存在。 [2]
之前社区努力添加条件表达式因缺乏关于最佳语法的共识而受阻。这个问题通过简单地推迟到 BDFL 的最佳判断来解决。
该决定通过审查语法在整个标准库中的应用情况得到了验证(该审查近似于对各种应用程序的实际用例的抽样,由许多具有不同背景的程序员编写)。 [3]
将对语法进行以下更改。(or_test 符号是新的,其他符号已修改。)
test: or_test ['if' or_test 'else' test] | lambdef
or_test: and_test ('or' and_test)*
...
testlist_safe: or_test [(',' or_test)+ [',']]
...
gen_for: 'for' exprlist 'in' or_test [gen_iter]
新的语法几乎引入了轻微的语法向后不兼容。在以前的 Python 版本中,以下内容是合法的
[f for f in lambda x: x, lambda x: x**2 if f(1) == 1]
(即,列表推导,其中 “in” 之后的序列是不带括号的 lambda 系列——甚至只是一个 lambda。)
在 Python 3.0 中,lambda 系列需要用括号括起来,例如
[f for f in (lambda x: x, lambda x: x**2) if f(1) == 1]
这是因为 lambda 的绑定比 if-else 表达式更松散,但在这种情况下,lambda 已经可以后跟一个绑定更松散的 “if” 关键字(有关详细信息,请考虑上面显示的语法更改)。
但是,在 Python 2.5 中,使用了一种略微不同的语法,该语法与向后兼容性更高,但通过禁止 lambda 主体包含不带括号的条件表达式来限制了在这种位置使用的 lambda 的语法。示例
[f for f in (1, lambda x: x if x >= 0 else -1)] # OK
[f for f in 1, (lambda x: x if x >= 0 else -1)] # OK
[f for f in 1, lambda x: (x if x >= 0 else -1)] # OK
[f for f in 1, lambda x: x if x >= 0 else -1] # INVALID
参考资料
PEP 初稿简介(出于历史原因保留)
关于 if-then-else(“三元”)表达式的请求不断出现在 comp.lang.python 上。本 PEP 包含一个关于相当 Pythonic 语法的具体提案。这是社区唯一的机会:如果本 PEP 以明显的多数通过,它将在 Python 2.4 中实现。如果没有,本 PEP 将会补充拒绝的原因摘要,该主题最好不要再提。虽然 BDFL 是本 PEP 的合著者,但他既不支持也不反对该提案;由社区决定。如果社区无法决定,BDFL 将拒绝该 PEP。
在社区前所未有的响应(对赞成和反对意见都提出了非常好的论点)之后,本 PEP 在 Raymond Hettinger 的帮助下进行了修订。在不进行完整修订历史记录的情况下,主要更改包括不同的建议语法、对建议备选方案的概述、当前讨论的状态以及对短路行为的讨论。
在讨论之后,进行了投票。虽然大家普遍希望使用某种形式的 if-then-else 表达式,但没有一种格式能够获得大多数支持。因此,由于缺乏压倒性多数支持更改,该 PEP 被拒绝。此外,Python 设计原则一直是当对采取哪条路径有疑问时,优先选择现状。
提案
建议的语法如下
(if <condition>: <expression1> else: <expression2>)
请注意,封闭括号不是可选的。
由此产生的表达式按以下方式计算
- 首先,计算 <condition>。
- 如果 <condition> 为真,则计算 <expression1> 并且它是整个表达式的结果。
- 如果 <condition> 为假,则计算 <expression2> 并且它是整个表达式的结果。
这种语法的自然扩展是允许一个或多个 ‘elif’ 部分
(if <cond1>: <expr1> elif <cond2>: <expr2> ... else: <exprN>)
如果提案被接受,这将被实现。
该提案的缺点是
- 所需的括号
- 与语句语法的混淆性
- 冒号的额外语义负载
请注意,最多只计算 <expression1> 和 <expression2> 之一。这被称为 “短路表达式”;它类似于 ‘and’ / ‘or’ 的第二个操作数仅在第一个操作数为真 / 假时才计算的方式。
模拟 if-then-else 表达式的一种常用方法是
<condition> and <expression1> or <expression2>
但是,这与实际情况不符:当 <expression1> 为假时,它返回 <expression2>!有关可行备选方案,请参见 FAQ 4.16——但是,它们非常丑陋,需要更多精力才能理解。
备选方案
Holger Krekel 提出了一种新的、侵入性最小的变体
<condition> and <expression1> else <expression2>
它背后的理念是,使用 and/or 已经存在一个几乎完整的三元运算符,而该提案是对其进行的最小的入侵性更改,使其变得完整。许多新闻组的回复者发现这是最令人满意的备选方案。但是,一些回复者能够发布在精神上难以解析的示例。后来有人指出,这种结构是通过让 “else” 改变 “and” 的现有含义来实现的。
因此,Christian Tismer 提出的同一想法的变体正在获得越来越多的支持
<condition> then <expression1> else <expression2>
优点是简单的视觉解析、不需要括号、现有关键字的语义没有改变、与语句语法混淆的可能性不像提案那样大,并且不会进一步重载冒号。缺点是引入新关键字的实现成本。但是,与其他新关键字不同,“then” 这个词似乎不太可能在现有程序中用作名称。
—
许多 C 衍生语言使用这种语法
<condition> ? <expression1> : <expression2>
Eric Raymond 甚至实现了这一点。BDFL 拒绝了它,原因有几个:冒号在 Python 中已经有许多用途(尽管实际上它不会产生歧义,因为问号需要一个匹配的冒号);对于不习惯 C 衍生语言的人来说,它很难理解。
—
本 PEP 的原始版本提出了以下语法
<expression1> if <condition> else <expression2>
许多参与讨论的人发现这种乱序排列太不舒服了;尤其是在 <expression1> 很长的情况下,在浏览时很容易错过条件。
—
有些人建议添加一个新的内置函数,而不是扩展语言的语法。例如
cond(<condition>, <expression1>, <expression2>)
这将无法像语法扩展那样工作,因为在调用函数之前必须计算 expression1 和 expression2。没有办法对表达式计算进行短路。如果 ‘cond’(或其他名称)被设为关键字,它可以工作,但这具有添加新关键字的所有缺点,以及令人困惑的语法:它 **看起来** 像函数调用,因此非专业读者可能会期望计算 <expression1> 和 <expression2>。
讨论现状摘要
各组落入以下三种阵营之一
- 采用使用标点符号构建的三元运算符
<condition> ? <expression1> : <expression2>
- 采用使用新的或现有的关键字构建的三元运算符。领先的示例是
<condition> then <expression1> else <expression2> (if <condition>: <expression1> else: <expression2>)
- 什么也不做。
前两种立场比较相似。
有些人认为,任何形式的标点符号都会使语言更加隐晦。另一些人则认为,标点符号样式适用于表达式而不是语句,并且有助于避免 COBOL 样式:3 加 4 乘以 5。
适应现有的关键字试图通过明确的含义和更整洁的外观来改进标点符号。缺点是失去标点运算符提供的某种程度的表达式经济性。另一个缺点是,它会在关键字的两种含义和两种用法之间产生一定程度的混淆。
通过引入新的关键字,这些困难得到了克服,而引入新的关键字需要更多工作来实现。
最后一种立场是,什么也不做。赞成意见包括保持语言简单和简洁;保持向后兼容性;以及每个用例都可以在 “if” 和 “else” 的基础上表达出来。lambda 表达式是一个例外,因为它们需要将条件分解成单独的函数定义。
反对不采取行动的论据是,其他选择允许更经济的表达方式,并且目前的实践表明 “and”、 “or” 或它们更复杂、视觉上不太吸引人的解决方法存在错误使用倾向。
短路行为
三元运算符与 cond()
函数的主要区别在于,后者提供了一种表达式形式,但不提供短路计算。
在三种情况下,短路计算是可取的
- 当表达式具有副作用时
- 当一个或两个表达式都非常消耗资源时
- 当条件充当表达式有效性的保护措施时。
# Example where all three reasons apply
data = isinstance(source, file) ? source.readlines()
: source.split()
readlines()
移动文件指针- 对于长源代码,两种备选方案都需要时间
split()
仅对字符串有效,而readlines()
仅对文件对象有效。
支持 cond()
函数的人指出,对短路计算的需求很少。通过扫描现有的代码目录,他们发现 if/else 出现的频率并不高;在这些代码中,只有少数代码包含可以用 cond()
或三元运算符改进的表达式;并且大多数这些代码不需要短路计算。因此,cond()
能够满足大多数需求,并且能够避免改变语言语法的努力。
更多支持证据来自对 C 代码库的扫描,这些扫描表明,其三元运算符使用的频率非常低(作为代码行百分比)。
对该分析的反驳是,三元运算符的可用性在每种情况下都帮助了程序员,因为它省去了搜索副作用的必要性。此外,它将阻止由引入副作用的远距离修改引起的错误。随着属性的出现,这种情况已经成为现实,即使属性访问也可以赋予副作用。
BDFL 的立场是,短路行为对于向语言添加 if-then-else 结构至关重要。
投票结果详情
Votes rejecting all options: 82
Votes with rank ordering: 436
---
Total votes received: 518
ACCEPT REJECT TOTAL
--------------------- --------------------- -----
Rank1 Rank2 Rank3 Rank1 Rank2 Rank3
Letter
A 51 33 19 18 20 20 161
B 45 46 21 9 24 23 168
C 94 54 29 20 20 18 235
D 71 40 31 5 28 31 206
E 7 7 10 3 5 32
F 14 19 10 7 17 67
G 7 6 10 1 2 4 30
H 20 22 17 4 10 25 98
I 16 20 9 5 5 20 75
J 6 17 5 1 10 39
K 1 6 4 13 24
L 1 2 3 3 9
M 7 3 4 2 5 11 32
N 2 3 4 2 11
O 1 6 5 1 4 9 26
P 5 3 6 1 5 7 27
Q 18 7 15 6 5 11 62
Z 1 1
--- --- --- --- --- --- ----
Total 363 286 202 73 149 230 1303
RejectAll 82 82 82 246
--- --- --- --- --- --- ----
Total 363 286 202 155 231 312 1549
选择键
A. x if C else y
B. if C then x else y
C. (if C: x else: y)
D. C ? x : y
E. C ? x ! y
F. cond(C, x, y)
G. C ?? x || y
H. C then x else y
I. x when C else y
J. C ? x else y
K. C -> x else y
L. C -> (x, y)
M. [x if C else y]
N. ifelse C: x else y
O. <if C then x else y>
P. C and x else y
Q. any write-in vote
写进投票及其排名详情
3: Q reject y x C elsethenif
2: Q accept (C ? x ! y)
3: Q reject ...
3: Q accept ? C : x : y
3: Q accept (x if C, y otherwise)
3: Q reject ...
3: Q reject NONE
1: Q accept select : (<c1> : <val1>; [<cx> : <valx>; ]* elseval)
2: Q reject if C: t else: f
3: Q accept C selects x else y
2: Q accept iff(C, x, y) # "if-function"
1: Q accept (y, x)[C]
1: Q accept C true: x false: y
3: Q accept C then: x else: y
3: Q reject
3: Q accept (if C: x elif C2: y else: z)
3: Q accept C -> x : y
1: Q accept x (if C), y
1: Q accept if c: x else: y
3: Q accept (c).{True:1, False:2}
2: Q accept if c: x else: y
3: Q accept (c).{True:1, False:2}
3: Q accept if C: x else y
1: Q accept (x if C else y)
1: Q accept ifelse(C, x, y)
2: Q reject x or y <- C
1: Q accept (C ? x : y) required parens
1: Q accept iif(C, x, y)
1: Q accept ?(C, x, y)
1: Q accept switch-case
2: Q accept multi-line if/else
1: Q accept C: x else: y
2: Q accept (C): x else: y
3: Q accept if C: x else: y
1: Q accept x if C, else y
1: Q reject choice: c1->a; c2->b; ...; z
3: Q accept [if C then x else y]
3: Q reject no other choice has x as the first element
1: Q accept (x,y) ? C
3: Q accept x if C else y (The "else y" being optional)
1: Q accept (C ? x , y)
1: Q accept any outcome (i.e form or plain rejection) from a usability study
1: Q reject (x if C else y)
1: Q accept (x if C else y)
2: Q reject NONE
3: Q reject NONE
3: Q accept (C ? x else y)
3: Q accept x when C else y
2: Q accept (x if C else y)
2: Q accept cond(C1, x1, C2, x2, C3, x3,...)
1: Q accept (if C1: x elif C2: y else: z)
1: Q reject cond(C, :x, :y)
3: Q accept (C and [x] or [y])[0]
2: Q reject
3: Q reject
3: Q reject all else
1: Q reject no-change
3: Q reject deliberately omitted as I have no interest in any other proposal
2: Q reject (C then x else Y)
1: Q accept if C: x else: y
1: Q reject (if C then x else y)
3: Q reject C?(x, y)
版权
本文档已置于公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0308.rst