Python赋值运算符与非赋值运算符有所不同

20
我遇到了一个奇怪的行为,找不到解释。
MWE:
l = [1]
l += {'a': 2}
l
[1, 'a']
l + {'B': 3}
Traceback (most recent call last):
  File "<input>", line 1, in <module>
TypeError: can only concatenate list (not "dict") to list

基本上,当我使用+=时,Python不会引发错误并将键值添加到列表中,而当我只计算+时,它会产生预期的TypeError

注意:这是Python 3.6.10。


1
请查看此链接:https://dev59.com/S2Up5IYBdhLWcg3wS2LP。 - Mayur Fartade
2
请参阅 https://bugs.python.org/issue9314 - “连接列表和迭代器时结果不一致”。此外,请注意 += 等被称为“增强赋值”,来自引入它们的 PEP 203。 - alkasm
那是件奇怪的事情。 - Dhruv Agarwal
6
我认为@alkasm提供的错误链接有一个很好的解释:“当a可变时,a += b会原地更新它,因此没有歧义:a的类型不会改变。当你使用a + b时,在选择结果类型时,没有理由把a视为比b更重要。” - Chris Doyle
是的,我只是在这里添加了一个注释,以便清楚地解释为什么会出现这种行为,因为链接中有相当多的信息。但是,这不是一个错误,而是预期的行为,注释只是帮助解释它为什么像这样工作。 - Chris Doyle
显示剩余2条评论
2个回答

17

l += ... 实际上调用了 object.__iadd__(self, other) 并在 l 可变时以原地修改的方式进行操作。

之所以会这样(正如@DeepSpace在评论中解释的那样),是因为当您执行 l += {'a': 2} 操作时,只有在 l 是可变类型时才会就地更新 l。另一方面,操作 l + {'a': 2} 不会原地进行,从而导致 list + dictionary -> TypeError


(参见此处

l = [1]
l = l.__iadd__({'a': 2})
l
#[1, 'a']

这不同于调用object.__add__(self, other)+

l + {'B': 3}
TypeError: can only concatenate list (not "dict") to list

7
这也应该解释了原因。l += ...在原地更新l,因此结果的类型很清楚(即l的类型)。l + ...是不明确的,因为它不在原地进行操作,所以新对象的类型不清楚(它应该是l的类型还是...的类型?) - DeepSpace
1
当然!我已经在我的答案中添加了这个。l += ...原地操作。 - seralouk
@seralouk,你介意分享一下你用了哪个工具来找出调用了哪个函数吗?是Inspect、pdb还是其他的? - surya
文档:https://docs.python.org/3/reference/datamodel.html#object.__iadd__,以及一些编程知识,例如字典是可变的。 - seralouk
@DeepSpace,这种推理是有道理的,但是__iadd__不能保证返回相同的对象。请参见number += 2。此外,从文档中可以看到:这些方法应该尝试原地执行操作(修改self)并返回结果(可以是self,也可以不是)。 - Capi Etheriel
@CapiEtheriel,这不是关于返回相同的对象,而是关于返回相同的类型。此外,int不是一个好的例子,因为它是一个特殊情况,a = 1; a += 2.0,现在a是浮点数。但是,int本身是不可变的,所以“原地”没有意义。 - DeepSpace

1

作者说这不是一个bug。当你执行 a += b 时,就像 b 来到了 a 的家里,并以 a 喜欢的方式进行更改。作者所说的是,当你执行 a + b 时,不能确定哪一个样式将得到优先处理。在执行之前,没有人知道 a + b 的结果会去哪里。因此,你无法决定使用谁的样式。如果是 a 的样式,则为 [1,'a'],如果是 b 的样式,则会出现错误。因此,不能确定谁将获得优先权。 我个人不同意这种说法,因为当你调用堆栈时,a 在比 b 更高的位置。当有一个表达式像 a + b 时,你首先调用 a.__add__(self, other),如果 a.__add__NotImplemented(在这种情况下它是实现的),那么你就会调用 a.__radd__(self, other),这意味着在这种情况下调用 other.__add__,也就是 b.__add__。我是基于调用堆栈的位置来说明这一点的,而Python社区可能有更重要的原因来做这件事。

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