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早期草案引言(为历史目的保留)
在 comp.lang.python 上不断出现对 if-then-else(“三元”)表达式的需求。本 PEP 包含了一个相当 Python 风格的语法的具体提案。这是社区的唯一机会:如果本 PEP 以明确的多数票通过,它将在 Python 2.4 中实现。如果未通过,PEP 将补充拒绝理由的总结,并且最好不再提及该主题。虽然 BDFL 是本 PEP 的共同作者,但他既不赞成也不反对该提案;由社区决定。如果社区无法决定,BDFL 将拒绝 PEP。
在社区前所未有的回应(正反两方面都有很好的论据)之后,本 PEP 在 Raymond Hettinger 的帮助下进行了修订。在不进行完整的修订历史回顾的情况下,主要变化是提出了不同的语法,概述了替代方案,当前讨论的状态,以及对短路行为的讨论。
讨论结束后,进行了一次投票。尽管大家普遍希望有某种形式的 if-then-else 表达式,但没有一种格式能获得多数支持。因此,由于缺乏压倒性多数支持变革,该 PEP 被驳回。此外,Python 的一项设计原则是,在对采取哪条路径存在疑问时,倾向于维持现状。
提案
建议的语法如下
(if <condition>: <expression1> else: <expression2>)
请注意,外围括号不是可选的。
结果表达式的求值方式如下
- 首先,评估 <条件>。
- 如果 <条件> 为真,则评估 <表达式1>,并将其作为整个表达式的结果。
- 如果 <条件> 为假,则评估 <表达式2>,并将其作为整个表达式的结果。
这种语法的一个自然扩展是允许一个或多个“elif”部分
(if <cond1>: <expr1> elif <cond2>: <expr2> ... else: <exprN>)
如果提案被接受,这将得到实施。
该提案的缺点是
- 所需的括号
- 与语句语法的混淆性
- 冒号的额外语义负载
请注意,<表达式1> 和 <表达式2> 最多只有一个会被求值。这称为“短路表达式”;它类似于 'and' / 'or' 的第二个操作数仅在第一个操作数为真 / 假时才被求值的方式。
模拟 if-then-else 表达式的常见方法是
<condition> and <expression1> or <expression2>
然而,这并不能以相同的方式工作:当 <表达式1> 为假时,它返回 <表达式2>!请参阅 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>
讨论中的许多参与者都觉得这种不按顺序的排列方式太不舒服;特别是当 <表达式1> 很长时,快速浏览时很容易错过条件。
—
有人建议添加一个新的内置函数而不是扩展语言语法。例如
cond(<condition>, <expression1>, <expression2>)
这不会像语法扩展那样工作,因为在调用函数之前必须评估 expression1 和 expression2。没有办法短路表达式评估。如果 'cond'(或某些其他名称)成为关键字,它可能会起作用,但这具有添加新关键字的所有缺点,加上令人困惑的语法:它**看起来**像一个函数调用,所以一个随便的读者可能会期望
当前讨论情况总结
各方分为三类
- 采用使用标点符号构建的三元运算符
<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
最后修改: 2025-02-01 08:59:27 GMT