tags:

views:

116

answers:

8

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?

A: 

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.

carl
But the documentation says `|=` is synonymous to `update`. Also, the call to `|=` does update the set, it only throws the exception afterward.
Space_C0wb0y
synonymous != identical :)
carl
+1  A: 
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.

gruszczy
See my edit for explanation of the downvote.
Space_C0wb0y
OK, I am equally startled by your example now. I have no idea, why |= can't be used on a tuple.
gruszczy
+1  A: 

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]
>>> 
msalvadores
A: 

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

Shwetanka
u -> you, r -> are. Please.
PreludeAndFugue
A: 

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

S.Lott
+9  A: 
>>> 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.

jchl
That is a nice explanation. I have learned something new :-)
gruszczy
Interesting, but I don't understand why it works if you instead say `z = x[0]; z |= y`. If `z` and `x[0]` are the same object then it's strange that it works for one and not the other?
Scott Griffiths
@Scott Griffiths: the fact that `z` and `x[0]` are the same object does not mean that `z |= y` and `x[0] |= y` should do the same thing. Consider `x = [0]; y = 1; z = x[0]; z |= y`.
jchl
Thanks, good example. I need to sit down in a darkened room and think about this :)
Scott Griffiths
A: 

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

Matt Joiner
But the `|=` on `foo[0]` will call `__ior__`, not `__or__` because sets are mutable, so your first line is not equivalent to `foo[0] = foo[0] | set(range(5))`.
Scott Griffiths
@Scott Griffiths: Right you are
Matt Joiner
A: 
  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.

ddaa