0

I did not expect this to work, since I was modifying the object being iterated over, but I didn't expect it to fail this way. I actually expected an exception to be raised.

>>> x = [1, 2, 3]
>>> for a in x:
...   print a, x.pop(0)
... 
1 1
3 2
>>> x
    [3]

With a slightly larger range:

>>> x = [1, 2, 3, 4]
>>> for a in x:
...   print a, x.pop(0)
... 
1 1
3 2
>>> x
    [3, 4]

And a little bit larger still:

>>> x = [1, 2, 3, 4, 5]
>>> for a in x:
...   print a, x.pop(0)
... 
1 1
3 2
5 3
>>> x
    [4, 5]

It's like the for loop is creating a generator from the list, but comparing the "index" to the length of the list to decide when iteration is over.

It still seems like this should generate an exception though, not this bizarre behavior. Is there some reason it doesn't raise an exception?

boatcoder
  • 17,525
  • 18
  • 114
  • 178
  • A very interesting question, given that mutating a dict being iterated over *will* raise an exception. – Ignacio Vazquez-Abrams Feb 24 '15 at 21:48
  • Not a duplicate. Those ask what happens, this asks why Python allows it to happen. – Ignacio Vazquez-Abrams Feb 24 '15 at 21:49
  • He's iterating over the list, not a dict – Gillespie Feb 24 '15 at 21:50
  • Because that's the way Guido designed Python. You can modify a list while iterating over it, which you can't do in many other languages. – dursk Feb 24 '15 at 22:09
  • Well, whether or not this question deserves to stay open, I think it's at least worth explicitly pointing folks to one of the leading ["duplicate candidates"](http://stackoverflow.com/questions/17299581/loop-forgets-to-remove-some-items) because there is some useful info there. In particular, it is mentioned that the `for` loop, as applied to lists, is implemented under the covers as an index-based iteration (presumably for simplicity or efficiency). So it would seem this is a case of *practicality beats purity*. – John Y Feb 24 '15 at 22:18

1 Answers1

0

As you have intuited, the for loop is using an index internally, incrementing it by 1 on each iteration, and stopping when the index exceeds the length of the list. This is how iteration is defined for lists. It is defined in other ways for other types, and you can define it for your own classes by implementing __iter__.

Modifying a list while iterating over it is legal, and predictable once you understand how it works. When you remove one or more items, the items after it shift down to lower indexes, and if the item removed is at an index less than or equal to the loop's current index, you end up skipping items when the loop increments the index.

There are a number of solutions: iterate in reverse order, iterate over a copy, make a list of the indices to be deleted and do that separately, build a new list instead of modifying the existing one, use a while loop instead of an if statement if you are conditionally removing items... there are probably other approaches as well.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • I also found these docs on the python website over the weekend https://docs.python.org/2/reference/compound_stmts.html#for Interestingly enough, not under the for statement though. – boatcoder Feb 27 '15 at 21:33