为什么更新元组中的集会导致错误?

时间:2010-08-16 10:13:38

标签: python tuples

我刚刚在Python 2.6中尝试了以下内容:

>>> foo = (set(),)
>>> foo[0] |= set(range(5))
TypeError: 'tuple' object does not support item assignment
>>> foo
(set([0, 1, 2, 3, 4]),)
>>> foo[0].update(set(range(10)))
>>> foo
(set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),)

我在这里有几个问题:

  • 为什么foo[0] |= set(range(5))更新集合并抛出异常?
  • foo[0].update(set(range(10)))为什么没有问题?它应该与第一个声明的结果不一样吗?

编辑很多人都指出,元组是不可变的。我知道这一点。他们还指出,|=会创建一个新的set对象并将其分配给元组。那是错的。见:

>>> foo = set()
>>> bar = foo
>>> foo is bar
True
>>> foo |= set(range(5))
>>> foo
set([0, 1, 2, 3, 4])
>>> bar
set([0, 1, 2, 3, 4])
>>> foo is bar
True

这意味着没有创建新对象,但现有对象已被修改。这应该与元组一起使用。另请注意,尽管我的第一个代码抛出TypeError,但元组中的集仍然会更新。这就是我感兴趣的效果。为什么TypeError,当操作明显成功时?

8 个答案:

答案 0 :(得分:11)

>>> def f():
...   x = (set(),)
...   y = set([0])
...   x[0] |= y
...   return   
... 
>>> import dis
>>> dis.dis(f)
  2           0 LOAD_GLOBAL              0 (set)
              3 CALL_FUNCTION            0
              6 BUILD_TUPLE              1
              9 STORE_FAST               0 (x)

  3          12 LOAD_GLOBAL              0 (set)
             15 LOAD_CONST               1 (0)
             18 BUILD_LIST               1
             21 CALL_FUNCTION            1
             24 STORE_FAST               1 (y)

  4          27 LOAD_FAST                0 (x)
             30 LOAD_CONST               1 (0)
             33 DUP_TOPX                 2
             36 BINARY_SUBSCR       
             37 LOAD_FAST                1 (y)
             40 INPLACE_OR          
             41 ROT_THREE           
             42 STORE_SUBSCR        

  5          43 LOAD_CONST               0 (None)
             46 RETURN_VALUE        

这表明语句x[0] |= y是通过调用x[0].__ior__(y)然后将返回值分配给x[0]来实现的。

set通过|=返回set.__ior__来实施就地self。但是,x[0]的分配仍然存在。它分配已经存在的相同值的事实是无关紧要的;它失败的原因与:

x = (set(),)
x[0] = x[0]

失败。

答案 1 :(得分:2)

在您的示例中,foo是一个元组。 python中的Tuples是不可变的,这意味着您无法更改任何元组元素的引用 - foo[0]在您的情况下。以下情况无法完成:

>>> x = ('foo','bar')
>>> x[0]='foo2'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> 

您可以使用list代替

>>> foo = [set(),None]
>>> foo
[set([]), None]
>>> foo[0] |= set(range(5))
>>> foo
[set([0, 1, 2, 3, 4]), None]
>>> 

答案 2 :(得分:1)

foo[0] |= set(range(5)) 

不起作用,因为你想要达到的目的是:

foo[0] = foo[0] | set(range(5))

并且您不能将新元素分配给旧元组,因为它们是不可变的。例如,你不能这样做:

x = (0, 1, 2)
x[0] = 3

当您运行更新时,不会更改元组中的引用,而只会更改引用后面的对象。你也可以这样做:

x = set()
y = (x,)
x.update(set(range(5))

正如您所看到的,您不会更改元组,但x(和y[0])将会更改。

x |= y

x.update(y)

不一样,因为update适用,x |= y会创建一个新对象(x | y)并将其存储在名称x下。

答案 3 :(得分:0)

元组是不可变的。通过尝试分配给foo[0],您尝试更改元组存储的值(对集合的引用)。使用update()函数时,不是更改引用,而是更改实际集。因为参考是相同的,所以这是允许的。

答案 4 :(得分:0)

元组是不可变的,所以你不能为它重新分配值。但是如果一个元组包含一个可变类型,例如list或set,你可以更新它们。 现在在你的情况下当你使用'| ='u实际上首先更新集合(这是元组中的值)然后将其分配给导致异常的元组。 更新集后抛出异常。

在下一种情况下,你只需更新集合,这样就没有异常。 请参阅http://docs.python.org/reference/datamodel.html

答案 5 :(得分:0)

  

“为什么TypeError,当操作显然是成功的?”。

因为有多种副作用。尽量避免这种情况。

这就是

  

foo [0] | = set(range(5))更新集合并抛出异常

正确。首先完成设定的突变。

然后尝试了元组突变并失败。

  

foo [0] .update(set(range(10)))工作没有问题?

正确。该集变异了。

  

它是否应该与第一个声明的结果相同?

没有。第一个语句涉及显式赋值 - 更改元组 - 这是禁止的。

第二个语句更新了一个不可变元组的成员,这个操作不是被禁止的,但在推送信封时是可疑的。

法家学者争辩说,他们不应该是一样的吗?还是类似的?否。

禁止更新元组对象(通过赋值)。

不禁止更新现有元组对象的成员(通过mutator函数)。

答案 6 :(得分:0)

解释这个的最好方法是“代数地”显示它:

foo[0] |= set(range(5))
foo[0] = set.__ior__(foo[0], set(range(5)))
tuple.__setitem__(foo, 0, set.__ior__(foo[0], set(range(5))))

foo[0].update(set(range(5)))
set.__ior__(foo[0], set(range(5)))

如您所见,update表单不一样,它会修改foo[0]__or__从左右操作数的元素生成一个新集。然后将其分配回foo

请注意,为简单起见,不会扩展对问题没有帮助的扩展(例如foo[0] -> tuple.__getitem__(foo, 0))。

引发的TypeError位于tuple.__setitem__tuple不允许替换其项目引用。 update表单不会以任何方式触及foo(即它不会调用tuple.__setitem__)。

答案 7 :(得分:0)

  1. a |= b相当于a = operator.ior(a, b)
  2. s[i] |= b相当于s[i] = operator.ior(s[i], b)
  3. 合同禁止对元组进行项目分配。
  4. set.__ior__方法调用set.update而不创建新实例。
  5. 这解释了您正在观察的行为。

    根本问题是,更改元组的值是违反合同的。你不应该尝试这样做。因为你可以在元组中有任何对象,所以你可以利用漏洞,但是你会得到你正在观察的那种奇怪的行为。

    元组项应该是frozenset而不是set。如果这样做,您将获得一致的行为,并且不会对错误产生不必要的副作用。