为啥你可以在 for 循环中循环一个隐式元组,但在 Python 中却不能?

Posted

技术标签:

【中文标题】为啥你可以在 for 循环中循环一个隐式元组,但在 Python 中却不能?【英文标题】:Why can you loop through an implicit tuple in a for loop, but not a comprehension in Python?为什么你可以在 for 循环中循环一个隐式元组,但在 Python 中却不能? 【发布时间】:2017-04-28 21:19: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

这是有原因的吗?我不确定正确的术语,所以我的搜索没有任何用处。

更新:

根据 cmets,此语法 适用于 Python 2.x,但不适用于 Python 3.x。

【问题讨论】:

我从不喜欢隐式元组,所以我觉得两者都很糟糕,但这个问题很有趣 : 有助于识别隐式元组的结尾吗? 它适用于 Python 2.7 但不适用于 Python 3 @nigel222:这不会有歧义,因为将其解释为迭代"abc",那么"def" 的无关逗号连接子句无论如何都是非法的语法;列表推导式也不能定义列表中的离散项。 @Scott:PEP 是特定于函数调用规则的;在开始理解之前,没有什么可以阻止通过打包元组来调用列表理解。 Py3 列表组合被实现为以类似函数的方式调用的嵌套范围,但它们没有在代码中定义的参数列表,它们没有以 PEP 禁止的方式解包参数,因此 PEP 不会申请。 【参考方案1】:

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

对于 for 循环和列表推导,使用不带括号的元组时不会有歧义,因为前者总是以冒号结尾,而后者则以右括号或 for/if 关键字结尾。

但是,生成器表达式的部分设计要求它们可以“裸”用作函数参数:

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

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

>>> 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 历史博客中写了一篇文章,详细阐述了这个主题的所有细节:

From List Comprehensions to Generator Expressions

【讨论】:

【参考方案2】:

因为第一个代码中的for i in 与第二个代码中的for i in 的语法结构不同。

第一种情况是a for statement, which has the grammar:

for_stmt ::=  "for" target_list "in" expression_list ":" suite
             ["else" ":" suite]

'a', 'b', 'c' 绝对是expression_list,这样就可以了。

然而,在第二种情况下,方括号内的内联 for 强制将代码解释为列表解析,而在 Python 3 中,list comprehensions must have the syntax:

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,但逗号分隔的表达式会创建 expression lists,而表达式列表不能是 or_test --- 或者换句话说,@987654338 @ 的优先级高于逗号。因此,Python 认为理解以逗号结尾,因此列表的三个元素是:

i for i in 'a'
'b'
'c'

其中(除非您将i for i in 'a' 放在括号中)显然是无效的。

至于为什么这在 Python 2 中有效……我还在寻找。

【讨论】:

明显无效吗? Python 允许列出任意对象,例如[[1,2], "cat", Date]。当然,这可能会令人困惑。 根据@ekhumoro's answer,它很可能在 Python 2 中工作,因为列表推导是在 Python 2 中发明的,在广义推导语法和生成器表达式存在之前。它们的行为不像生成器表达式、集合/字典推导等,将变量泄漏到周围范围内,允许不带括号的迭代等。在 Python 3 中,为了简化和一致性,列表推导被更改为更类似于包装列表构造函数中的geneexpr,并且geneexpr限制是由副作用继承的。 至于“为什么这在 Python 2 中有效”:语法明确允许逗号分隔的列表:old_expression 基本上,genexpr 有一个很好的理由来禁止无括号的元组(如答案中所述,当您重用函数调用括号将genexpr 作为函数的单个参数包装时,它会产生问题),和 listcomps 遵循相同的规则,因此在从一个切换到另一个时没有微妙的问题。【参考方案3】:

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

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

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

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

for 没有这种歧义 - 所以它不需要括号。

【讨论】:

以上是关于为啥你可以在 for 循环中循环一个隐式元组,但在 Python 中却不能?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这个for循环不执行?

找不到参数元组的隐式值

for循环中,为啥不执行循环?

无法使用 for 循环迭代元组

为啥我的 For 循环在 EJS 节点 js 中只返回一个值?

for 循环中,step 步长 有啥作用,为啥要设置 步长