不可变容器内的可变类型

15 投票
3 回答
1290 浏览
提问于 2025-04-17 12:29

我对修改元组里的成员有点困惑。下面这个代码不行:

>>> 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 个回答

1

你不能单独替换元组中的某个元素,但你可以替换整个元素的内容。这样做是可以的:

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

你不能直接修改元组(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()。它实际上是先进行加法运算,然后再进行赋值(而赋值会失败),而不是简单地在原地添加内容。

17

你可以随时修改元组里面的可变值。你看到的奇怪现象是因为

>>> 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']

撰写回答