【问题标题】:a mutable type inside an immutable container不可变容器内的可变类型
【发布时间】:2012-02-28 15:29:09
【问题描述】:

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

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

那么游戏的规则到底是什么,什么时候可以修改元组内的东西,什么时候不能修改?这似乎更像是禁止对元组成员使用赋值运算符,但最后两种情况让我感到困惑。

【问题讨论】:

标签: python list tuples immutability mutable


【解决方案1】:

您可以始终修改元组内的可变值。你用

看到的令人费解的行为
>>> 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

所以元组仍然有对 same 列表的引用,但是列表已经就地修改了:

>>> id(thing[0])
3074072428L
>>> thing[0] 
['b', 'c', 'd']

【讨论】:

  • 为什么thing[0] += 'e' 不等同于thing[0] = thing[0] + ['e'],它会构造正确的列表然后在分配失败时丢失它?也就是说,如果它简化为.extend('e'),那为什么还要使用tmp?为什么不只是thing[0].extend('e')
  • @ChrisLutz:是的,好点。你完全正确:它不等于thing[0].extend('e'),而是你建议的thing[0] = thing[0] + 'e'。我已经确定了答案。
  • 我仍然不明白我标记为“不工作,并且工作”的情况。我希望它要么 a) 工作,修改列表,要么 b) 引发异常并且不修改列表。它如何因异常而失败并修改值?
  • @wim:我已经更新了答案以强调出了什么问题:首先执行INPLACE_ADD(这会改变列表),然后执行STORE_FAST(当您尝试分配时失败到一个元组)。
【解决方案2】:

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

( + ,)       <--- 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() - 它实际上是先进行加法然后再赋值(并且赋值失败),而不是仅仅在原地追加。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多