为什么在Python的for循环中可以遍历隐式元组,但不能在推导式中遍历?

17
在使用 for 循环遍历隐式元组时没有问题,但是在列表推导中这样做会导致语法错误,是否有什么原因呢?
例如:
for i in 'a','b','c': 
    print(i)

'a'
'b'
'c'

但是总的来说:

>>> [i for i in 'a','b','c']
  File "<stdin>", line 1
    [i for i in 'a','b','c']
                   ^
SyntaxError: invalid syntax

这是有原因的吗?我不确定正确的术语,所以我的搜索没有得到有用的结果。

更新:

根据评论,这个语法在Python 2.x中是有效的,但在Python 3.x中是无效的。


3
我从未喜欢过“隐式元组”,所以两者对我来说都不太好看,但是问题很有趣。 - Ma0
3
在Python 2.7中可以工作,但在Python 3中无法运行。 - Moinuddin Quadri
适用于2.7版本,不适用于3.5版本。 - ayhan
1
@nigel222:这并不会产生歧义,因为将其解释为迭代“abc”,然后是一个无关的逗号连接子句“def”也会是非法语法;列表推导式不能在列表中定义离散项。 - ShadowRanger
2
@Scott:该 PEP 特定于函数调用规则;在开始推导之前,通过打包元组来调用列表推导式是可以的。Py3 列表推导式实现为以函数方式调用的嵌套作用域,但它们没有代码定义的参数列表,也没有按照 PEP 禁止的方式解包其参数,因此该 PEP 不适用。 - ShadowRanger
显示剩余4条评论
3个回答

24

这在Python3中有所改变,主要是为了使列表推导更符合生成器表达式的一致性。

使用for循环和列表推导时,如果使用没有括号的元组,就不会产生歧义,因为前者总是以冒号结尾,而后者则以闭合括号或for/if关键字结尾。

然而,生成器表达式的设计的一部分要求它们可以“裸露”地用作函数参数:

>>> list(i for i in range(3))
[0, 1, 2]

这会给未加括号的元组带来一些歧义,因为逗号可能会引入一个新的参数:

这会使得未加括号的元组在解释时变得不够明确,因为逗号可能会被解释为引入一个新的参数:

which creates some ambiguity for unparenthesized tuples, because any commas may introduce a new argument:

=>

这会使得未加括号的元组在解释时变得不够明确,因为逗号可能会被解释为引入一个新的参数:

>>> list(i for i in 0, 1, 2)
  File "<stdin>", line 1
SyntaxError: Generator expression must be parenthesized if not sole argument

因此,在生成器表达式中必须始终将元组括起来,为了保持一致性,现在同样适用于列表推导式。

PS:

Guido van Rossum在他的Python历史博客中编写了一篇详细说明这个主题的文章:


3
因为第一个代码中的 for i in 与第二个代码中的 for i in 是不同的语法结构。
第一种情况是一个 for 循环语句,其语法为:
for_stmt ::=  "for" target_list "in" expression_list ":" suite
             ["else" ":" suite]
'a', 'b', 'c' 明显是一个 expression_list,所以这样可以运行。
然而,在第二个例子中,方括号内的内联 for 强制将代码解释为列表推导式,在 Python 3 中,列表推导式必须具有以下语法
comprehension ::=  expression comp_for
comp_for      ::=  "for" target_list "in" or_test [comp_iter]
comp_iter     ::=  comp_for | comp_if
comp_if       ::=  "if" expression_nocond [comp_iter]

请注意,in关键字后面必须是or_test,然而使用逗号分隔的表达式会创建表达式列表,而表达式列表不能是or_test,换句话说,or比逗号的优先级更高。因此,Python 认为推导式在逗号处结束,所以列表的三个元素是:
i for i in 'a'
'b'
'c'

除非你将i for i in 'a'放在括号中,否则它是无效的。

至于为什么这在Python 2中有效...我还在研究中。


它是否显然无效?Python允许任意对象的列表,例如[[1,2], "cat", Date]。这可能会令人困惑,但确实是有效的。 - nigel222
1
根据@ekhumoro的答案,它在Python 2中可能有效,因为在广义推导语法和生成器表达式存在之前,列表推导是在Python 2中发明的。它们不像生成器表达式、集合/字典推导等那样行为,会将变量泄漏到周围的作用域中,允许未加括号的可迭代对象等。在Python 3中,为了简化和一致性,列表推导被改成了更接近于将genexpr包装在列表构造函数中的形式,并且继承了genexpr的限制。 - ShadowRanger
1
关于“为什么Python 2可以这样工作”的问题:语法明确允许逗号分隔的列表:old_expression - dhke
基本上,genexpr 有一个很好的理由禁止未加括号的元组(正如答案中所指出的,当您重复使用函数调用括号将 genexpr 包装为函数的单个参数时,它会创建问题),而 listcomps 遵循相同的规则,因此在从一个转换到另一个时不会有微妙的陷阱。 - ShadowRanger

0

我认为问题出在这里:在后一种情况下,你正在迭代哪些对象并不那么明显:

>>> [i for i in ('a','b','c')]
['a', 'b', 'c']

元素之间的边界在哪里?它是一个由生成器和两个整数组成的3个元素的数组吗?就像这样:

>>> [(i for i in 'a'),'b','c']
[<generator object <genexpr> at 0x10cefeeb8>, 'b', 'c']

for不会有这种歧义 - 因此它不需要括号。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接