不可变容器中的可变类型

a mutable type inside an immutable container

提问人:wim 提问时间:2/7/2012 最后编辑:wim 更新时间:5/11/2020 访问量:1226

问:

我对修改元组成员有点困惑。以下方法不起作用:

>>> 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赞 wim 10/26/2019
注意:这现在在 Python 常见问题解答

答:

6赞 Amber 2/7/2012 #1

您不能修改元组,但可以修改元组中包含的内容。列表(以及集合、字典和对象)是一种引用类型,因此元组中的“事物”只是一个引用 - 实际列表是一个可变对象,由该引用指向,可以在不更改引用本身的情况下进行修改。

( + ,)       <--- 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()

17赞 Martin Geisler 2/7/2012 #2

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

>>> 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_FASTINPLACE_ADD

这就解释了为什么“Doesn't work, and works”的情况会留下修改后的列表:元组已经引用了该列表:

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

然后,该列表由 和 失败:INPLACE_ADDSTORE_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赞 Lawrence D'Oliveiro 2/7/2012 #3

不能替换元组的元素,但可以替换元素的全部内容。这将起作用:

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