开发者

Why does updating a set in a tuple cause an error?

开发者 https://www.devze.com 2023-01-11 13:18 出处:网络
I have just tried the following in Python 2.6: >>> foo = (set(),) >>> foo[0] |= set(range(5))

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?


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


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


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.


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.


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


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


  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.


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

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号