views:

720

answers:

5

Hi, I'm currently developing a program in python and I just noticed that something was wrong with the foreach loop in the language, or maybe the list structure. I'll just give a generic example of my problem to simplify, since I get the same erroneous behavior on both my program and my generic example:

x = [1,2,2,2,2]

for i in x:
    x.remove(i)

print x

Well, the problem here is simple, I though that this code was supposed to remove all elements from a list. Well, the problem is that after it's execution, I always get 2 remaining elements in the list.

What am I doing wrong? Thanks for all the help in advance.

Edit: I don't want to empty a list, this is just an example...

+15  A: 

This is a well-documented behaviour in Python, that you aren't supposed to modify the list being iterated through. Try this instead:

for i in x[:]:
    x.remove(i)

The [:] returns a "slice" of x, which happens to contain all its elements, and is thus effectively a copy of x.

Chris Jester-Young
See also: http://docs.python.org/tutorial/controlflow.html#for-statements
Chris Jester-Young
Thanks, that was it :)
rogeriopvl
This doesn't explain why in rogeriopvl's question the list contains two elements. If this explanation was true, the list should have been intact
bgbg
@bgbg: "it is not safe" explains that behavior perfectly. A vague spec like that is a kind way of saying 'undefined'
TokenMacGuy
+6  A: 

When you delete an element, and the for-loop incs to the next index, you then skip an element.

Do it backwards. Or please state your real problem.

Erik
+3  A: 

Why don't you just use:

x = []

It's probably because you're changing the same array that you're iterating over.

Try Chris-Jester Young's answer if you want to clear the array your way.

fluffels
+1  A: 

I think, broadly speaking, that when you write:

for x in lst:
    # loop body goes here

under the hood, python is doing something like this:

i = 0
while i < len(lst):
    x = lst[i]
    # loop body goes here
    i += 1

If you insert lst.remove(x) for the loop body, perhaps then you'll be able to see why you get the result you do?

Essentially, python uses a moving pointer to traverse the list. The pointer starts by pointing at the first element. Then you remove the first element, thus making the second element the new first element. Then the pointer move to the new second – previously third – element. And so on. (it might be clearer if you use [1,2,3,4,5] instead of [1,2,2,2,2] as your sample list)

John Fouhy
+1  A: 

I agree with John Fouhy regarding the break condition. Traversing a copy of the list works for the remove() method, as Chris Jester-Young suggested. But if one needs to pop() specific items, then iterating in reverse works, as Erik mentioned, in which case the operation can be done in place. For example:

def r_enumerate(iterable):
    """enumerator for reverse iteration of an iterable"""
    enum = enumerate(reversed(iterable))
    last = len(iterable)-1
    return ((last - i, x) for i,x in enum)

x = [1,2,3,4,5]
y = []
for i,v in r_enumerate(x):
    if v != 3:
        y.append(x.pop(i))
    print 'i=%d, v=%d, x=%s, y=%s' %(i,v,x,y)


or with xrange:

x = [1,2,3,4,5]
y = []
for i in xrange(len(x)-1,-1,-1):
    if x[i] != 3:
        y.append(x.pop(i))
    print 'i=%d, x=%s, y=%s' %(i,x,y)
eryksun