为啥你可以在 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 中却不能?的主要内容,如果未能解决你的问题,请参考以下文章