2012-02-07 80 views
8

我对修改元组成员有点困惑。以下不工作:不可变容器内的可变类型

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

那么究竟是游戏规则,当你能和不能修改一个元组里面的东西?这似乎更像是禁止对元组成员使用赋值运算符,但后两种情况令我困惑。

回答

7

你可以总是修改一个元组内的可变值。你

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

为什么'thing [0] + ='e''等于thing [0] = thing [0] + ['e']',这会构建正确的列表然后在分配失败时丢失它?也就是说,如果它简化为'.extend('e')',为什么'tmp'呢?为什么不只是'东西[0] .extend('e')'? – 2012-02-07 07:31:50

+0

@ChrisLutz:是的,好点。你完全正确:它不等同于'thing [0] .extend('e')',而是等同于你所说的'thing [0] = thing [0] +'e''。我已经解决了这个问题的答案。 – 2012-02-07 08:27:01

+0

我仍然不明白我标记为“不工作,有效”的情况。我希望它可以a)工作,修改列表或者b)引发异常并且不修改列表。它如何失败,并发生异常并修改该值? – wim 2012-02-08 04:11:39

2

您不能修改元组,但可以修改元组中包含的内容。列表(包括集合,字典和对象)是引用类型,因此“事物”元组只是一个引用 - 实际列表是该引用指向的可变对象,可以是修改而不改变参考本身。

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

重新ason为什么+=的错误在于它不完全等同于.append() - 它实际上会做一个添加,然后是一个赋值(并且赋值失败),而不是仅仅在原地添加。

1

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

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