1

Is this piece of code potentially dangerous? Will it mess up with the inner and outer iterations?

for a in listA:
            for b in listB: 
                if [... something...]:
                    ... something else...
                    listA.remove(a)
                    listB.remove(b)
                    break
Ricky Robinson
  • 21,798
  • 42
  • 129
  • 185
  • 3
    Yes and yes: it probably won't behave as you want it to. See http://stackoverflow.com/questions/1207406/remove-items-from-a-list-while-iterating-in-python – Emily Feb 15 '13 at 16:20
  • 1
    perhaps try this: `for b in listb[:]:` it will just make a copy of the list and you can just remove from your original list. – IT Ninja Feb 15 '13 at 16:27
  • Yeah, that sounds a lot better than having to do everything through list comprehensions. I do quite many things in the "... something else" above and it would be a bit cumbersome to compress everything like suggested in the above link. – Ricky Robinson Feb 15 '13 at 16:32

3 Answers3

6

Is this piece of code potentially dangerous? Depends. Reducing the size of a sequence while iterating over it would give unexpected behavior.

Consider this Example

listA = [1,2,3,4]

>>> for a in listA:
    listA.remove(a)
    print a

Because, on removing the items, all the items beyond it, are pushed towards the left, the item that you are supposing to iterate would automatically move to the next element

First Iteration:

    listA = [1,2,3,4]
             ^
             |
_____________|

    listA.remove(a)


    listA = [2,3,4]
             ^
             |
_____________|


    print a
    (outputs) 1

Second Iteration:

    listA = [2,3,4]
               ^
               |
_______________|

    listA.remove(a)

    listA = [2,4]
               ^
               |
_______________|


    print a
    (outputs) 3

Third Iteration:

    listA = [2,4]
                 ^
                 |
_________________|

(Exits the Loop)
Abhijit
  • 62,056
  • 18
  • 131
  • 204
  • Oh so I'm actually skipping items, which is exactly the behaviour I was experiencing. Thank you so much for the explanation. – Ricky Robinson Feb 15 '13 at 16:29
2

Changing a sequence being iterated over is generally an anti-pattern in Python. While you can dance around it in specific cases, it's best to see if you can construct a new list (or dict) containing only the items you need.

jeffknupp
  • 5,966
  • 3
  • 28
  • 29
  • Actually I would delete both `a` and `b` at the same time and then call `break`, which jumps me out of the inner loop and move to the next element in `listA`. – Ricky Robinson Feb 15 '13 at 16:25
  • Ah, missed the break. You're correct, I'll edit my answer (the general principle is still applicable). – jeffknupp Feb 15 '13 at 16:28
1

I agree with jknupp - removing items in a list can be more expensive than creating a new one. However, another trick is to do it backwards:

>>> l = range(5)
>>> for a in reversed(l):
...     print a
...     l.remove(a)
... 
4
3
2
1
0
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • Uhm. Will reversed create a local copy of the list? Otherwise I dont see the difference. – Ricky Robinson Feb 15 '13 at 17:20
  • No, reversed() creates a reverse iterator. Its name is kinda misleading, but at least its not too verbose. By iterating backwards, you've already passed all of the elements that will be displaced by the remove(). – tdelaney Feb 15 '13 at 19:50