提问人:wim 提问时间:2/7/2012 最后编辑:wim 更新时间:5/11/2020 访问量:1226
不可变容器中的可变类型
a mutable type inside an immutable container
问:
我对修改元组成员有点困惑。以下方法不起作用:
>>> 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'],)
那么,游戏规则究竟是什么,什么时候可以修改元组内部的东西,什么时候不能修改元组内部的东西呢?这似乎更像是禁止对元组成员使用赋值运算符,但最后两种情况让我感到困惑。
答:
您不能修改元组,但可以修改元组中包含的内容。列表(以及集合、字典和对象)是一种引用类型,因此元组中的“事物”只是一个引用 - 实际列表是一个可变对象,由该引用指向,可以在不更改引用本身的情况下进行修改。
( + ,) <--- 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
这就解释了为什么“Doesn't work, and works”的情况会留下修改后的列表:元组已经引用了该列表:
>>> 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']
不能替换元组的元素,但可以替换元素的全部内容。这将起作用:
thing[0][:] = ['b']
评论