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

3 投票
8 回答
2777 浏览
提问于 2025-04-16 02:51

我刚刚在 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 个回答

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 这个名字下。

2

在你的例子中,foo 是一个元组。元组在Python中是不可变的,这意味着你不能改变元组中任何元素的引用——在你的例子中就是 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]
>>> 
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]

失败。

撰写回答