5

I want to understand why the following is happening. My guess is that a temporary is being created during list iteration, but want some experts to confirm this:

def test():
    a=[set([1,2,3]),set([3,4,5])]
    x=set([1,4])
    for i in a:
        # doesn't actually modify list contents, making a copy of list elements in i?
        i=i.difference(x)
    print a
    for idx,i in enumerate(a):
        i=i.difference(x)
        print id(i),id(a[idx])
        # obviously this modifies the contents
        a[idx]=i
    print a

Output:

[set([1, 2, 3]), set([3, 4, 5])]
59672976 59672616
59672616 59672736
[set([2, 3]), set([3, 5])]

Also, I want to understand why the "id" of i in the second iteration is the same as the "id" for a[0].

Sid
  • 7,511
  • 2
  • 28
  • 41
  • Related question: http://stackoverflow.com/questions/1637807/modifying-list-while-iterating – Steven T. Snyder Mar 21 '12 at 15:23
  • @Series8217 I already saw this one but that is more about modified the actual sequence itself while iterating whereas I'm talking about modifying the contents of the sequence. Thanks though. – Sid Mar 21 '12 at 15:24

4 Answers4

4

It helps to look at this graphically, because it's basically a pointer problem.

for i in a iteratively assigns i to each element in a.

iteration

i = i.difference(x) creates new object and assigns i to it.

assignment

Steven T. Snyder
  • 5,847
  • 4
  • 27
  • 58
2

Let's take this one step at a time:

  1. i.difference(x) doesn't modify i or x. Rather, it returns a new set.
  2. i = i.difference(x) rebinds the variable i to point to the new set. It does not affect the contents of the list in any way.
  3. a[idx] = i does modify the list by setting its idx-th element to the new set.

A cleaner implementation might use a different variable instead of re-purposing i:

def test():
    a=[set([1,2,3]),set([3,4,5])]
    x=set([1,4])
    for i in a:
        diff=i.difference(x)
        # a[idx]=diff
    print a
NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • So i is a temporary, a copy of one of the elements of the sequence, right? – Sid Mar 21 '12 at 15:26
  • @Sid: In Python there's no such thing as a *temporary* (in the C++ sense). What your code does is **reuse** the variable called `i` to store the result of `i.difference(x)`. See my updated answer for a cleaner way to code the same thing. – NPE Mar 21 '12 at 15:30
  • Actually, LCs and genexes *do* produce a sort of temporary. – Ignacio Vazquez-Abrams Mar 21 '12 at 15:36
  • @aix Can you take a look at my update regarding the "id" of "i" vs. that of a[0]? Is that what you mean by "reuse"? – Sid Mar 21 '12 at 15:38
  • 1
    @Sid: no, it's a reference to one of the elements in the sequence. If this element happens to be a mutable object, you can modify it in place. For example, suppose `i` was a list, then `i.append(1)` would append 1 to the list that's inside the set. Assigning to `i` only re-binds the name `i` to another object. The confusion arises from the fact that for immutable objects such as integers, there is no perceived difference between assignment and mutation. – André Caron Mar 21 '12 at 15:52
2

Yes, when you execute i=i.difference(x) it creates a new i. Just modify your code like this to understand what is happening -

def test():
    a=[set([1,2,3]),set([3,4,5])]
    x=set([1,4])
    for i in a:
        # doesn't actually modify list contents, making a copy of list elements in i?
        print 'old i - ', id(i)
        i=i.difference(x)
        print 'new i - ', id(i)
    print a

test()

Output -

old i -  4467059736
new i -  4467179216
old i -  4467177360
new i -  4467179216
[set([1, 2, 3]), set([3, 4, 5])]
ronakg
  • 4,038
  • 21
  • 46
  • I added the line print id(i),id(a[idx]) and got the following ids: 59672976 59672616 59672616 59672736 So obviously the ids are different for i vs. the corresponding a[idx] but what I found weird was that the id for a[0] got assigned to i in the next iteration. Maybe this is what @aix means by reuse? – Sid Mar 21 '12 at 15:36
1

Your use of set.difference() suggests that you don't know the operator -= for sets:

def test():
    a=[set([1,2,3]),set([3,4,5])]
    x=set([1,4])
    for i in a:
        i -= x
    print a

This shows that i is just another pointer to the set you want to modify. Just don't overwrite your pointer!

Alfe
  • 56,346
  • 20
  • 107
  • 159
  • I find this semantically bizarre that i-=x actually modifies the pointee while i=i-x modifies the pointer. Traditionally one would expect i-=x and i=i-x to be equivalent, no? – Sid Mar 21 '12 at 15:41
  • 1
    @Sid `=` is the assignment operator. `-=` is an "augmented" assignment, which evaluates the target only once, and modifies it in-place where possible. Refer to the documentation for [augmented assignment expressions](http://docs.python.org/reference/simple_stmts.html#augmented-assignment-statements). – Steven T. Snyder Mar 21 '12 at 16:28
  • There's a good reason why the two syntaxes `i = i - x` and `i -= x` have also different methods (namely `__sub__()` and `__isub__()` ;-) – Alfe Mar 21 '12 at 16:35
  • @Series8217 Thanks, that section was very helpful. – Sid Mar 21 '12 at 16:48