我刚刚在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
,当操作明显成功时?
答案 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)
a |= b
相当于a = operator.ior(a, b)
。s[i] |= b
相当于s[i] = operator.ior(s[i], b)
。set.__ior__
方法调用set.update
而不创建新实例。这解释了您正在观察的行为。
根本问题是,更改元组的值是违反合同的。你不应该尝试这样做。因为你可以在元组中有任何对象,所以你可以利用漏洞,但是你会得到你正在观察的那种奇怪的行为。
元组项应该是frozenset
而不是set
。如果这样做,您将获得一致的行为,并且不会对错误产生不必要的副作用。