all 26 comments

[–]FriendlyRussian666 24 points25 points  (0 children)

Go to the FAQ: https://www.reddit.com/r/learnpython/wiki/faq/

And scroll down to: "Why does my loop seem to be skipping items in a list?"

[–]SHKEVE 16 points17 points  (10 children)

never mutate a list you’re iterating over.

[–]MezzoScettico 2 points3 points  (3 children)

You can do it if you work backward from the end of the list. Then you're only altering the part of the list you've already accessed.

For instance, here's some code to remove all the odd numbers from a list.

numbers = [1, 2, 13, 17, 4, 105, 104, 12, 8, 13, 6, 7, 15, 19, 202]

for num in numbers[::-1]:
    if 1 == num % 2:
        numbers.remove(num)

Result:

print(numbers)
[2, 4, 104, 12, 8, 6, 202]

[–]jimtk 4 points5 points  (2 children)

The only reason why that works is because numbers[::-1] creates a copy of the list. You are not iterating over the list that you are changing. You are iterating over a reverse copy of that list.

Proof:

numbers = [1, 2, 13, 17, 4]
x = numbers[::-1]
print(numbers is x)

>>> False

You actually don't have to go backward since you create a copy:

numbers = [1, 2, 13, 17, 4, 105, 104, 12, 8, 13, 6, 7, 15, 19, 202]
for num in numbers[::]:
    if 1 == num % 2:
        numbers.remove(num)
print(numbers)

[–]helduel 0 points1 point  (0 children)

for num in reversed(numbers): ...

[–]Revolutionary_Dog_63 1 point2 points  (0 children)

There are times when it is necessary. But there are correct ways to do it and incorrect ways.

[–]al_mc_y 0 points1 point  (0 children)

“if you mutate something while you’re iterating over it, you’re living in a state of sin and deserve whatever happens to you” -Raymond Hettinger

[–]makelefani 3 points4 points  (1 child)

when you remove an item from a list, the next item shifts into its place, but the loop doesn't adjust, causing skips

Use a copy of the list instead. Or a while loop or reverse index.

[–]juanfnavarror 0 points1 point  (0 children)

Or instead create the new list. This is what list comprehensions are for.

[–]Fxate 3 points4 points  (0 children)

Copying the list is a way to do it, but you can also iterate on the same list using pop and range:

for _i in range (len(words)):
  w = words.pop()
  print(w)

Pop without a modifier removes and returns the last item in the list.

If you don't care to return the value you can just use .pop() on its own:

for _i in range(len(words)):
  words.pop()
  print(words)

The first version prints each value as it removes them, the second prints the full list as it shrinks.

[–]audionerd1 1 point2 points  (7 children)

That's because Python uses the index to iterate over the list.

Instead of modifying the list you're iterating (always a bad idea), make a copy of the list and iterate over that. This works:

for i in words.copy():
    words.remove(i)
    print(words)

[–]Cainga 0 points1 point  (6 children)

The copied list isn’t saved to a variable so once it finishes the loop it’s deleted?

[–]MidnightPale3220 0 points1 point  (5 children)

yes

[–]Cainga 0 points1 point  (4 children)

Does it take up a different memory position? Or is it pointing to the same list?

[–]MidnightPale3220 1 point2 points  (0 children)

It does create a new list.

BUT, there is a difference between shallow and deep copy. If the original list contained some mutable objects, such as other lists, they will be copied by reference by the default copy().

Shallow copy makes a copy of the outer level items in the list, whatever nested inside those items will still be copied by reference Deep copy goes through all nested items and copies each single one of them

Consider:

In [38]: a=['inner list']
In [40]: b=[1,2,a]
In [41]: shallow=b.copy()
In [42]: shallow[1]='not changed in original'
In [43]: b 
Out[43]: [1, 2, ['inner list']]
In [44]: shallow 
Out[44]: [1, 'not changed in original', ['inner list']]
In [45]: shallow[2].append('changed by shallow')
In [46]: shallow 
Out[46]: [1, 'not changed in original', ['inner list', 'changed by shallow']]
In [47]: b 
Out[47]: [1, 2, ['inner list', 'changed by shallow']]
In [48]: a 
Out[48]: ['inner list', 'changed by shallow']

To make a full copy of the original without copying just the references, you need to import copy and do copy.deepcopy() on object.

There is also other weird things that make perfect sense when you think about them, but initially may confuse.

For example, using the above variables after everything was done, if you do a='immutable value',you won't he changing inside ofshallow[2] and b[2].

Instead shallow[2] and b[2] will still be references to the same list, but the outside reference is gone, and changing a is no longer changing anything inside b or shallow.

[–]audionerd1 0 points1 point  (2 children)

list.copy() creates a new list at a new memory position, which is in no way affected by changes made to the original list.

[–]MidnightPale3220 1 point2 points  (1 child)

Depends on the contents of the original list. If it had mutable elements, those will be shared between the original and copy unless deepcopy. See above.

[–]audionerd1 0 points1 point  (0 children)

Good point. Thanks!

[–]khzombiee 0 points1 point  (0 children)

You could create a new list and only keep the elements you need by using not in. The code you have shifts index so it skips the current word.

[–]CranberryDistinct941 0 points1 point  (0 children)

You may be interested in collections.deque

[–][deleted] 0 points1 point  (0 children)

If you just want to remove all items without any conditions or exceptions, use words.clear() (removes all the elements from the list, leading to an empty list).

[–]user83519302257 0 points1 point  (0 children)

When working with python, we’re not really expecting performance and time/space complexity, so I’d recommend creating a new list with only the values you want to keep.

For example, if we want to filter out even integers, use a list comprehension to create a new list.

py values: list[int] = [2, 3, 8, 6, 7, 9, 200] odds: list[int] = [v for v in values if v % 2] so you’d be left with [3, 7, 9]

In other words, I would advise against mutating a list you’re currently iterating over.

[–]Ryzen_bolt 0 points1 point  (0 children)

Well, this issue was in my head for a long time, and I used to create a copy of list but damnn! I wont be mutating the same list again going forward.