不可变容器内的可变类型

时间:2012-02-07 07:06:13

标签: python list tuples immutability mutable

我对修改元组成员感到有些困惑。以下不起作用:

>>> thing = (['a'],)
>>> thing[0] = ['b']
TypeError: 'tuple' object does not support item assignment
>>> thing
(['a'],)

但这确实有效:

>>> thing[0][0] = 'b'
>>> thing
(['b'],)

也有效:

>>> thing[0].append('c')
>>> thing
(['b', 'c'],)

不能正常工作(嗯?!):

>>> thing[0] += 'd'
TypeError: 'tuple' object does not support item assignment
>>> thing
(['b', 'c', 'd'],)

看似与以前相同,但有效:

>>> e = thing[0]
>>> e += 'e'
>>> thing
(['b', 'c', 'd', 'e'],)

那么,当你能够并且不能修改元组内的某些内容时,游戏的规则到底是什么?它似乎更像禁止使用赋值成员的赋值运算符,但最后两个案例让我感到困惑。

3 个答案:

答案 0 :(得分:9)

您可以始终修改元组内的可变值。你看到的令人费解的行为

>>> thing[0] += 'd'

+=引起。 +=运算符执行就地添加但 赋值 - 就地添加只能用于文件,但由于元组是不可变的,所以赋值失败。像

一样思考它
>>> thing[0] = thing[0] + 'd'

更好地解释了这一点。我们可以使用标准库中的dis module来查看从两个表达式生成的字节码。使用+=,我们得到INPLACE_ADD字节码:

>>> def f(some_list):
...     some_list += ["foo"]
... 
>>> dis.dis(f)
  2           0 LOAD_FAST                0 (some_list)
              3 LOAD_CONST               1 ('foo')
              6 BUILD_LIST               1
              9 INPLACE_ADD         
             10 STORE_FAST               0 (some_list)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE        

使用+,我们得到BINARY_ADD

>>> def g(some_list):
...     some_list = some_list + ["foo"]
>>> dis.dis(g)
  2           0 LOAD_FAST                0 (some_list)
              3 LOAD_CONST               1 ('foo')
              6 BUILD_LIST               1
              9 BINARY_ADD          
             10 STORE_FAST               0 (some_list)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE        

请注意,我们在两个位置都获得STORE_FAST。这是当您尝试存储回元组时失败的字节码 - 在工作正常之前出现的INPLACE_ADD

这解释了为什么“不工作,无效”的情况会使修改后的列表落后:元组已经有了对列表的引用:

>>> id(thing[0])
3074072428L

然后INPLACE_ADD修改了列表,STORE_FAST失败了:

>>> thing[0] += 'd'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

所以元组仍然引用了相同的列表,但列表已经就地修改了:

>>> id(thing[0])
3074072428L
>>> thing[0] 
['b', 'c', 'd']

答案 1 :(得分:2)

您无法修改元组,但可以修改元组中包含的内容。列表(以及集合,dicts和对象)是引用类型,因此元组中的“strong”只是一个引用 - 实际列表是一个可变对象该引用指向该引用,并且可以在不更改引用本身的情况下进行修改。

( + ,)       <--- your tuple (this can't be changed)
  |
  |
  v
 ['a']       <--- the list object your tuple references (this can be changed)

thing[0][0] = 'b'之后:

( + ,)       <--- notice how the contents of this are still the same
  |
  |
  v
 ['b']       <--- but the contents of this have changed

thing[0].append('c')之后:

( + ,)       <--- notice how this is still the same
  |
  |
  v
 ['b','c']   <--- but this has changed again

+=错误的原因是它不完全等同于.append() - 它实际上是添加然后是赋值(并且赋值失败),而不是仅仅追加到位。 / p>

答案 2 :(得分:1)

您不能替换元组的元素,但可以替换元素的全部内容。这将有效:

thing[0][:] = ['b']