不可变容器内的可变类型
我对修改元组里的成员有点困惑。下面这个代码不行:
>>> 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 个回答
你不能单独替换元组中的某个元素,但你可以替换整个元素的内容。这样做是可以的:
thing[0][:] = ['b']
你不能直接修改元组(tuple),但可以修改元组里面的内容。列表(list)、集合(set)、字典(dict)和对象(object)都是一种叫做引用类型的东西,所以元组里面的“东西”其实只是一个引用——真正的列表是一个可以被修改的对象,而这个引用指向它,你可以修改这个列表的内容,而不需要改变引用本身。
( + ,) <--- 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()
。它实际上是先进行加法运算,然后再进行赋值(而赋值会失败),而不是简单地在原地添加内容。
你可以随时修改元组里面的可变值。你看到的奇怪现象是因为
>>> thing[0] += 'd'
使用了+=
。这个+=
操作符不仅是进行原地加法,还涉及到赋值——原地加法是没问题的,但赋值失败,因为元组是不可变的。可以把它想象成
>>> thing[0] = thing[0] + 'd'
这样更容易理解。我们可以使用dis
模块,这是标准库的一部分,来查看这两个表达式生成的字节码。使用+=
时,我们得到的是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']