Anthony Labarre
Anthony Labarre

Reputation: 2794

Surprising behaviour when iterating over a list while modifying it

I'm aware that this is an example of code you should never write, and I'm not asking for the right approach. My concern is that I don't understand its behaviour, so I was hoping someone could shed light on it:

def foo(list_of_chars):
    '''Returns the set of words that can be obtained by removing one character from list_of_chars.'''
    result = set()
    my_copy = list(list_of_chars)
    for elem in my_copy:
        my_copy.remove(elem)
        result.add(''.join(my_copy))
        my_copy = list(list_of_chars)
    return result

I would have expected either behaviour from this function (let's say list_of_chars is ['h', 'e', 'l', 'l', 'o']):

  1. the code runs flawlessly because we restore my_copy at the end of each iteration, so when the for loop assigns the next element to elem it is the same as if we had never touched the list (so we do iterate over 'h', 'e', 'l', 'l', 'o');
  2. the code fails miserably because the assignment at the end of the loop is somehow ignored, so we first remove 'h', skip 'e', remove 'l', skip the next 'l', and then remove 'o' -- and possibly crash.

What actually happens is stranger: we iterate over 'h', 'l', 'l', 'o', so 'e' is skipped, but it's the only character that is skipped. Other examples behave in the same way: only the second element of list_of_chars is overlooked. Can someone explain this? (Python 2 and 3 yield the same result).

Upvotes: 0

Views: 43

Answers (1)

deceze
deceze

Reputation: 522109

The for loop does not "read" my_copy again on every iteration, but my_copy.remove does.

for elem in my_copy:
    my_copy.remove(elem)

On the first iteration, my_copy in both lines refers to the same object. You're actually modifying the object for iterates over. However, at the end of the iteration, you replace my_copy with something else. The for loop retains its original object reference, but my_copy.remove refers to the current version of my_copy. So now the object the for loop iterates over and the object that you remove an element from are two different objects.

Hence remove interferes with the loop only on the first iteration.

Upvotes: 4

Related Questions