开发者

how to safely remove elements from a list in Python

开发者 https://www.devze.com 2023-02-09 23:59 出处:网络
I loop through a list and remove the elements that satisfy my condition. But why doesn\'t this work, as noted below? Thank you.

I loop through a list and remove the elements that satisfy my condition. But why doesn't this work, as noted below? Thank you.

>>> a=[ i for i in range(4)]
>>> a
[0, 1, 2, 3]
>>> for e in a:
...     if (e > 1) and (e < 4):
...         a.remove(e)
... 
>>> a
[0, 1, 3]
>>> a=[ i for i in range(4)]
>>> f开发者_运维百科or e in a:
...     if (e > -1) and (e < 3):
...         a.remove(e)
... 
>>> a
[1, 3]


You cannot change something while you're iterating it. The results are weird and counter-intuitive, and nearly never what you want. In fact, many collections explicitly disallow this (e.g. sets and dicts).

Instead, iterate over a copy (for e in a[:]: ...) or, instead of modifying an existing list, filter it to get a new list containing the items you want ([e for e in a if ...]). Note that in many cases, you don't have to iterate again to filter, just merge the filtering with the generation of the data.


Why don't you just do this initially in the list comprehension? E.g.

[i for i in range(4) if i <= 1 or i >= 4]

You can also use this to construct a new list from the existing list, e.g.

[x for x in a if x <= 1 or x >= 4]


The idea of filtering is a good one, however it misses the point which is that some lists may be very large and the number of elements to remove may be very small.

In which case the answer is to remember the list indexes of the elements to remove and then iterate through the list of indexes, sorted from largest to smallest, removing the elements.


The easiest way to visualize it is to think of the iteration working on list-offsets instead of the actual items - do something to the first item, then the second item, then the third item, until it runs out of items. If you change the number of items in the list, it changes the offsets of all the remaining items in the list:

lst = [1,2,3,4]
for item in lst:
    if item==2:
        lst.remove(item)
    else:
        print item
print lst

results in

1
4
[1,3,4]

which makes sense if you step through it like so:

[1,2,3,4]
 ^
 first item is not 2, so print it -> 1

[1,2,3,4]
   ^
   second item is 2, so remove it

[1,3,4]
     ^
     third item is 4, so print it -> 4

The only real solution is do not change the number of items in the list while you are iterating over it. Copy the items you want to keep to a new list, or keep track of the values you want to remove and do the remove-by-value in a separate pass.


It is not safe to remove elements from a list while iterating though it. For that exists the filter function. It takes a function(that admits one argument) and an iterable(in this case your list). It returns a new iterable of the same type(list again here) with the elements where the function applied to that element returned True:

In your case you can use a lambda function like this:

a = filter(lambda x: x > 1 and x < 4, range(4))

or if you have the list already:

a = range(4)
a = filter(lambda x: x > 1 and x < 4, a)

remember that if you are using python3 it will return an iterator and not a list.

0

精彩评论

暂无评论...
验证码 换一张
取 消