追加到一个元组中定义的列表 – 这是一个错误?

所以我有这个代码:

tup = ([1,2,3],[7,8,9]) tup[0] += (4,5,6) 

这会产生这个错误:

 TypeError: 'tuple' object does not support item assignment 

虽然这个代码:

 tup = ([1,2,3],[7,8,9]) try: tup[0] += (4,5,6) except TypeError: print tup 

打印这个:

 ([1, 2, 3, 4, 5, 6], [7, 8, 9]) 

这是行为吗?

注意

我意识到这不是一个很常见的用例。 但是,虽然预期的错误,我没有想到列表的变化。

是的,这是预期的。

元组不能改变。 元组像列表一样是一个指向其他对象的结构。 它不关心这些对象是什么。 它们可以是string,数字,元组,列表或其他对象。

所以对元组中包含的对象之一做任何事情,包括追加到该对象,如果它是一个列表,是不相关的元组的语义。

(想象一下,如果你写了一个具有方法的类,导致它的内部状态发生改变,那么你就不会期望它不可能根据存储的地方调用这些方法)。

或者另一个例子:

 >>> l1 = [1, 2, 3] >>> l2 = [4, 5, 6] >>> t = (l1, l2) >>> l3 = [l1, l2] >>> l3[1].append(7) 

两个由列表和元组引用的可变列表。 我应该能够做最后一行(回答:是)。 如果你认为答案是否定的,为什么不呢? 不应该改变l3的语义(答案:否)。

如果你想要一个顺序结构的不可变对象,它应该是元组一直向下的。

为什么错误?

这个例子使用中缀运算符:

许多操作都有一个“就地”版本。 下面的函数比常用的语法提供了对原地操作符的更原始的访问; 例如,语句x + = y等价于x = operator.iadd(x,y)。 另一种说法是z = operator.iadd(x,y)等价于复合语句z = x; z + = y。

https://docs.python.org/2/library/operator.html

所以这:

 l = [1, 2, 3] tup = (l,) tup[0] += (4,5,6) 

相当于这个:

 l = [1, 2, 3] tup = (l,) x = tup[0] x = x.__iadd__([4, 5, 6]) # like extend, but returns x instead of None tup[0] = x 

__iadd__行成功,并修改第一个列表。 所以名单已经改变了。 __iadd__调用返回变异列表。

第二行尝试将列表分配回元组,并且失败。

所以,在程序结束时,列表已经被扩展,但是+=操作的第二部分失败了。 具体请看这个问题 。

那么我猜tup[0] += (4, 5, 6)被翻译成:

 tup[0] = tup[0].__iadd__((4,5,6)) 

tup[0].__iadd__((4,5,6))通常在第一个元素中改变列表。 但是由于元组是不可变的,所以赋值失败了。

元组不能直接更改,正确。 然而,你可以通过引用来改变元组的元素。 喜欢:

 >>> tup = ([1,2,3],[7,8,9]) >>> l = tup[0] >>> l += (4,5,6) >>> tup ([1, 2, 3, 4, 5, 6], [7, 8, 9]) 

Python开发者写了一个关于为什么发生在这里的官方解释: https : //docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when -the-加成作品

简短的版本就是+ =实际上做了两件事,一件接着一件:

  1. 运行在右边的东西。
  2. 将结果赋给左边的variables

在这种情况下,第1步工作,因为你可以添加到列表中的东西(他们是可变的),但第2步失败,因为你不能把东西后创build它们(元组是不可变的)。

在一个真正的程序中,我build议你不要尝试除了子句,因为tup[0].extend([4,5,6])完全一样。