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

发布于 2024-09-14 11:36:35 字数 1002 浏览 0 评论 0原文

我刚刚在 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

I have just tried the following in 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]),)

I have several questions here:

  • Why does foo[0] |= set(range(5)) update the set and throw an exception?
  • why does foo[0].update(set(range(10))) work without a problem? Should it not have the same result as the first statement?

Edit Many people have pointed out, that tuples are immutable. I am aware of that. They have also pointed out, that |= would create a new set object and assign it to the tuple. That is wrong. See this:

>>> 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

This means that no new object has been created, but the existing one was modified. This should work with the tuple. Please note also that, although my first code throws a TypeError, the set within the tuple is still updated. That is the effect I am interested in. Why the TypeError, when the operation obviously was successful?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

枫林﹌晚霞¤ 2024-09-21 11:36:36

元组是不可变的,因此您无法为其重新分配值。但是,如果元组包含可变类型(例如列表或集合),则可以更新它们。
现在,在您的情况下,当您使用“|=”时,您实际上首先更新集合(这是元组中的值),然后将其分配给导致异常的元组。
更新集合后抛出异常。

在下一种情况下,您只需更新集合,因此也不会有例外。
参考http://docs.python.org/reference/datamodel.html

Tuples are immutable so u cannot reassign values to it. But if a tuple contains a mutable type such as list or set u can update them.
now in your case when u use '|=' u actually first update the set (which is a value in the tuple) then assign it to tuple which causes the exception.
Exception is thrown after the updation of the set.

In the next case u r simply updating the set so there is no exception.
Refer to http://docs.python.org/reference/datamodel.html

幻想少年梦 2024-09-21 11:36:36

“为什么会出现 TypeError,而操作显然是成功的?”。

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

这就是

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

正确。首先完成集合突变。

然后尝试元组突变但失败。

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

正确的。该集合已突变。

它的结果不应该与第一个语句相同吗?

不可以。第一个语句涉及显式赋值——更改元组——这是被禁止的。

第二条语句更新了不可变元组的成员,该操作并未被禁止,但涉嫌挑战极限。

但是法家认为,它们不应该是一样的吗?或者类似?不可以。

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

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

"Why the TypeError, when the operation obviously was successful?".

Because there are multiple side-effects. Try to avoid that.

That's what the

foo[0] |= set(range(5)) update the set and throw an exception

Correct. First the set mutation is done.

Then the tuple mutation is attempted and fails.

foo[0].update(set(range(10))) work without a problem?

Correct. The set is mutated.

Should it not have the same result as the first statement?

No. The first statement involves explicit assignment -- changing the tuple -- which is forbidden.

The second statement updates a member of an immutable tuple, an operation that is not forbidden, but is suspicious as pushing the envelope.

But the Legalism Scholar argues, aren't they supposed to be the same? Or similar? No.

Updating the tuple object (via assignment) is forbidden.

Updating a member of an existing tuple object (via a mutator function) is not forbidden.

不即不离 2024-09-21 11:36:36
  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 而不创建新实例。

这解释了您所观察到的行为。

根本问题是,改变元组的值是违反契约的。你不应该尝试这样做。由于元组中可以包含任何对象,因此您可以利用一些漏洞,但随后您会得到您所观察到的奇怪行为。

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

  1. a |= b is equivalent to a = operator.ior(a, b).
  2. s[i] |= b is equivalent to s[i] = operator.ior(s[i], b).
  3. Item assignment on a tuple is forbidden by contract.
  4. The set.__ior__ method calls set.update without creating a new instance.

That explains the behaviour you are observing.

The underlying problem, is that changing the value of a tuple is a violation of contract. You should not try doing it. Since you can have any object in a tuple, there are loopholes you can exploit, but then you get the kind of weird behaviour you are observing.

Tuple items should be frozenset instead of set. If you do this, you will get a consistent behaviour, and no unwanted side-effect on error.

眼波传意 2024-09-21 11:36:36

解释这一点的最佳方法是“代数地”显示它:

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__)。

The best way to explain this is to show it "algebraically":

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)))

As you can see, the update form is not the same, it modifies foo[0] in place. __or__ generates a new set from the elements of the left and right operands. This is then assigned back to foo.

Note that for simplicity, the expansions that aren't helpful to the problem are not expanded (such as foo[0] -> tuple.__getitem__(foo, 0)).

The TypeError thrown is in tuple.__setitem__. tuple does not allow its items references to be replaced. The update form does not touch foo in any way (ie. it doesn't not invoke tuple.__setitem__).

九歌凝 2024-09-21 11:36:35
>>> 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]

失败。

>>> 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        

This shows that the statement x[0] |= y is implemented by calling x[0].__ior__(y) and then assigning the returned value to x[0].

set implements in-place |= by having set.__ior__ return self. However, the assignment to x[0] still takes place. The fact that it's assigning the same value that was already there is irrelevant; it fails for the same reason that:

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

fails.

嘦怹 2024-09-21 11:36:35

在您的示例中 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]
>>> 

In your example foo is a tuple. Tuples in python are inmutable, this means that you cannot change the reference of any tuple element - foo[0] in your case. Things like the following can't be done:

>>> 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
>>> 

You could use a list instead

>>> foo = [set(),None]
>>> foo
[set([]), None]
>>> foo[0] |= set(range(5))
>>> foo
[set([0, 1, 2, 3, 4]), None]
>>> 
月下凄凉 2024-09-21 11:36:35
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

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

doesn't work, because what you wanted to achieve is:

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

and you can't assign new elements to an old tuple, because they are immutable. For example you cant do this:

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

When you are running update, you don't change references in the tuple, but only object behind the reference. You could also do this like this:

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

as you can see you don't change the tuple, but x (and y[0]) will be changed.

x |= y

and

x.update(y)

aren't the same, because update works in place and x |= y will create a new object (x | y) and store it under name x.

木格 2024-09-21 11:36:35

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

Tuples are immutable. By trying to assign to foo[0], you are attempting to change a value that the tuple stores (a reference to a set). When you use the update() function, you are not changing the reference, but instead the actual set. Because the reference is the same, this is allowed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文