128

I have a list

a = ["a", "b", "c", "d", "e"]

I want to remove elements in this list in a for loop like below:

for item in a:
    print(item)
    a.remove(item)

But it doesn't work. What can I do?

Francisco
  • 10,918
  • 6
  • 34
  • 45
alwbtc
  • 28,057
  • 62
  • 134
  • 188
  • 2
    Why do you need to delete them at the same time? Just iterate through and then delete the whole list. Also do you actually need to print each item? – jamylak May 19 '12 at 13:33
  • But I rely on items in the list when I iterate over loop. I must get rid of the item immediately if it matches a condition – alwbtc May 19 '12 at 14:27

6 Answers6

181

You are not permitted to remove elements from the list while iterating over it using a for loop.

The best way to rewrite the code depends on what it is you're trying to do.

For example, your code is equivalent to:

for item in a:
    print(item)
a[:] = []

Alternatively, you could use a while loop:

while a:
    print(a.pop())

I'm trying to remove items if they match a condition. Then I go to next item.

You could copy every element that doesn't match the condition into a second list:

result = []
for item in a:
    if condition is False:
        result.append(item)
a = result

Alternatively, you could use filter or a list comprehension and assign the result back to a:

a = filter(lambda item:... , a)

or

a = [item for item in a if ...]

where ... stands for the condition that you need to check.

crypdick
  • 16,152
  • 7
  • 51
  • 74
NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • 1
    I'm trying to remove items if they match a condition. Then I go to next item. – alwbtc May 19 '12 at 13:35
  • 20
    "You are not permitted" - sure you are. Python doesn't complain at you, it just gives a slightly (read: completely) different result to what people might expect. – lvc May 19 '12 at 14:00
  • 5
    Why? Can someone point to the description of why this is the case? The list is reevaluated in each iteration, right? So what's the problem? – gamen Dec 05 '13 at 12:00
  • 7
    The problem is that python keeps an internal counter to remember the current elelement, which will refer to something unexpected when the list changes from under the iterator. (But iterating from back to front while removing might work as expected?) – Pascal Sep 18 '18 at 10:34
  • 2
    Using `.pop(0)` is very inefficient! It has a time complexity of O(N). Just use `pop()` which has time complexity O(1). – crypdick Sep 08 '20 at 21:26
  • That `while` loop is by far the best approach to solve for this problem, although I agree with what @crypdick said. There's no need to pop the zeroth element specifically. Just use `list.pop()`. – tionichm Apr 14 '22 at 11:04
  • @crypdick Well, your comment may be valid for `.pop(-1)` and `.pop()`, but comparing the efficiency of `.pop(0)` and `.pop()` doesn’t really make any sense… – Skippy le Grand Gourou Jan 12 '23 at 16:18
  • @SkippyleGrandGourou I don't understand your argument. `.pop(-1)` and `.pop()` are synonymous... – crypdick Feb 13 '23 at 22:40
  • @crypdick It was maybe poorly worded, but that was my point, `.pop(0)` and `.pop()` are **not** synonymous. But nevermind, I didn’t notice your comment was obsolete since you edited the question to remove a `.pop(0)` originally present in the answer (so it did make sense earlier indeed). – Skippy le Grand Gourou Feb 14 '23 at 09:34
110

Iterate through a copy of the list:

>>> a = ["a", "b", "c", "d", "e"]
>>> for item in a[:]:
    print(item)
    if item == "b":
        a.remove(item)

a
b
c
d
e
>>> print(a)
['a', 'c', 'd', 'e']
Francisco
  • 10,918
  • 6
  • 34
  • 45
Alex L
  • 8,748
  • 5
  • 49
  • 75
  • 5
    One problem with this and the OP's approaches is that they can break down if there are multiple elements that compare equal. – NPE May 19 '12 at 13:42
  • Awesome! This one works for me! – Ishtiaque Khan Oct 13 '15 at 20:47
  • 3
    @NPE I guess it depends on what you expect to happen. If you expect the code to remove all instances of "b" in the list, then it shouldn't break as [list.remove](https://docs.python.org/2/tutorial/datastructures.html#more-on-lists) removes only one item. – vikki Nov 11 '16 at 08:50
  • 2
    memory problems for a large list? – PirateApp Apr 21 '18 at 16:44
  • Any reason you're using `for item in a[:]:` instead of simply `for item in a:`? They appear to do the same thing. I understand that your example uses a copy, but is that really needed? – aggregate1166877 Aug 07 '19 at 00:37
  • 1
    @aggregate1166877 good question! in this case if we didn't iterate through the copy then deleting "b" would make `item` skip from `b` to `d` - so if we wanted to delete "b" or "c" then it would fail. quick explanation: https://gist.github.com/alexlouden/9f1ab4354d1c68ae4c1c94126ac51a20 – Alex L Aug 07 '19 at 02:11
  • @AlexL Ouch - subtle but deadly. Many thanks for the explanation – aggregate1166877 Aug 07 '19 at 05:34
32

As other answers have said, the best way to do this involves making a new list - either iterate over a copy, or construct a list with only the elements you want and assign it back to the same variable. The difference between these depends on your use case, since they affect other variables for the original list differently (or, rather, the first affects them, the second doesn't).

If a copy isn't an option for some reason, you do have one other option that relies on an understanding of why modifying a list you're iterating breaks. List iteration works by keeping track of an index, incrementing it each time around the loop until it falls off the end of the list. So, if you remove at (or before) the current index, everything from that point until the end shifts one spot to the left. But the iterator doesn't know about this, and effectively skips the next element since it is now at the current index rather than the next one. However, removing things that are after the current index doesn't affect things.

This implies that if you iterate the list back to front, if you remove an item at the current index, everything to it's right shifts left - but that doesn't matter, since you've already dealt with all the elements to the right of the current position, and you're moving left - the next element to the left is unaffected by the change, and so the iterator gives you the element you expect.

TL;DR:

>>> a = list(range(5))
>>> for b in reversed(a):
    if b == 3:
        a.remove(b)
>>> a
[0, 1, 2, 4]

However, making a copy is usually better in terms of making your code easy to read. I only mention this possibility for sake of completeness.

lvc
  • 34,233
  • 10
  • 73
  • 98
7
import copy

a = ["a", "b", "c", "d", "e"]

b = copy.copy(a)

for item in a:
    print(item)
    b.remove(item)
a = copy.copy(b)

Works: to avoid changing the list you are iterating on, you make a copy of a, iterate over it and remove the items from b. Then you copy b (the altered copy) back to a.

Francisco
  • 10,918
  • 6
  • 34
  • 45
user3848191
  • 103
  • 1
  • 6
  • wait, so .remove(...) can remove instances from "b" even though technically the instance being passed is an instance of "a"? – NoName Oct 10 '19 at 00:34
3

How about creating a new list and adding elements you want to that new list. You cannot remove elements while iterating through a list

cobie
  • 7,023
  • 11
  • 38
  • 60
1

Probably a bit late to answer this but I just found this thread and I had created my own code for it previously...

    list = [1,2,3,4,5]
    deleteList = []
    processNo = 0
    for item in list:
        if condition:
            print(item)
            deleteList.insert(0, processNo)
        processNo += 1

    if len(deleteList) > 0:
        for item in deleteList:
            del list[item]

It may be a long way of doing it but seems to work well. I create a second list that only holds numbers that relate to the list item to delete. Note the "insert" inserts the list item number at position 0 and pushes the remainder along so when deleting the items, the list is deleted from the highest number back to the lowest number so the list stays in sequence.

Francisco
  • 10,918
  • 6
  • 34
  • 45
Baz
  • 306
  • 1
  • 5
  • 12