Suppose you're iterating through a list, and you want to remove elements under some condition. This seems pretty easy: you've got an element, and python lists have a remove
method that you can call with that element.
However... let's try it with a simple example:
>> mylist = range(10)
>> for x in mylist:
print x
mylist.remove(x)
0
2
4
6
8
>> mylist
[1, 3, 5, 7, 9]
We've managed to only print and delete every other element of our list. The reason this happens is that, while we can loop through python lists without having to access elements by index, but that doesn't mean that the index isn't secretly there.
Suppose we rewrite our example using indexes to access the list:
>> mylist = range(10)
>> for i in range(len(mylist)):
print mylist[i]
mylist.remove(mylist[i])
0
2
4
6
8
IndexError: list index out of range
>> mylist
[1, 3, 5, 7, 9]
In this case, it's clear what the problem is. We delete the element at index i, which causes all the following elements to move over by one place: the element from index i+1 is now at position i. And then we increase i, skipping over the element that just moved into position i. (We also get an IndexError because, unlike the previous case, we don't stop iterating when we reach the end of the shortened list.)
The same thing happens in our first example: even though we don't use the index directly in our code, our for-loop is iterating through positions in an array, and thus ends up skipping elements when they change positions.
We can easily avoid this issue, however, by iterating over a copy of the list, rather than the list being modified:
>> mylist = range(10)
>> for x in mylist[:]:
print x
mylist.remove(x)
0
1
2
3
4
5
6
7
8
9
>> mylist
[]
mylist[:]
returns a copy of the whole list, which remains static while we delete elements from the original. And, thus, we get the intended result.