为什么在Python 3.8之前,在返回语句中使用星号迭代解包时没有括号是无效语法?

87
Python语言(特别是3.x版本)允许对可迭代对象进行非常通用的解包操作,其中一个简单的例子是:
a, *rest = 1, 2, 3

多年来,这种解包已逐渐被概括(例如参见PEP 3132PEP 448),使其可以在更多情况下使用。因此,我惊讶地发现以下内容在Python 3.6中是无效的语法(在Python 3.7中仍然如此):
def f():
    rest = [2, 3]
    return 1, *rest  # Invalid

我可以通过将返回的元组放在括号中来使其正常工作,如下所示:
def f():
    rest = [2, 3]
    return (1, *rest)  # Valid

我在return语句中使用这个似乎很重要,因为

t = 1, *rest

这确实是合法的,并且带括号和不带括号的结果相同。

Python开发人员是否忘记了这个案例,还是有任何原因导致此案例成为无效语法?

为什么我关心这个问题

这违反了我认为我与Python语言之间的重要契约。考虑以下(也有效的)解决方案:

def f():
    rest = [2, 3]
    t = 1, *rest
    return t

通常当我有像这样的代码时,我认为t是一个临时名称,我应该能够通过在底部一行中用其定义替换t来消除它。但在这种情况下,这会导致无效的代码。

def f():
    rest = [2, 3]
    return 1, *rest

当然,将返回值放在括号中并不是什么大问题,但通常只需要额外的括号来区分几个可能的结果(分组)。在这里,这不是问题,因为省略括号不会产生其他不想要的行为,而是根本没有行为。

更新

自Python 3.8以来(请参见此列表上的第7项),上述广义语法现在是有效的。


4
这实际上更多是由于语法句法导致的结果,而不是其他什么原因。 - cs95
你也不能只返回 *rest,这是无效的语法。 - lapisdecor
3
是的,但这与t = *rest是无效的事实一致。此外,return *restt = *rest并不表示任何实际的解包操作,因此我认为不允许这样做并不是一个问题。如果允许这样做,则仅用*rest将只是tuple(rest)的混淆语法。 - jmd_dk
这种情况不仅限于 return。在 yield 参数、下标、增量 赋值(但不是普通赋值)的 RHS 以及 for 语句中的 in 右侧,解包也是被禁止的。尽管在所有这些位置都允许使用未括号化元组,因为这些东西的语法使用的是 expression_list 而不是 starred_expression - user2357112
5
请注意 t = *restt = *rest, 之间的差异,后者是有效的。 - senderle
出于对一致性的考虑,为什么当我们将 x 孤立地传递到函数中时,它会在函数中映射为一个元组,但是当我们解包 a,x = iterable 时,它会作为列表解包到 x 中,并且不能孤立地完成(我理解从函数返回映射为元组,因为 a,b 是元组括号与否,但也为什么不能孤立地完成 return *x - Dan Boschen
1个回答

44

根据此次提交于Python 3.2中,我怀疑这是一个意外情况。

该提交启用了赋值表达式采用testlist_star_expr产生式(允许解包未括在括号内的列表),但保留了返回语句采用testlist产生式。我认为该提交只是忽略了这个问题(可能还有其他地方,但现在我专注于return_stmt产生式)。

我已经修改了Python语法/Grammar文件以支持此功能。所有测试都继续通过,包括test_grammar.py文件中的测试(但似乎还不够详尽)。

如果你感兴趣,这是我所做的更改。随时克隆或下载我的分支版本

更新:我已经提交了bpo问题拉取请求以解决返回(和yield)的解包问题。


3
你已经提交了这个更改的 pull request 吗? - Martijn Pieters
还没有。想看看这个解决了@jmd_dk想要的东西,也许在发送之前再看看其他几个情况(比如yield_stmt产生式)。 - David Cuthbert
2
如果您认为这只是一个意外,可能应该先提交错误报告,而不是创建一个“修复”。请前往 https://bugs.python.org/ 提交报告。 - Chris_Rands
4
修复将在Python 3.8中推出。 - Michael Scott Asato Cuthbert

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