问题
I lost a little bit of time in this Python for statement:
class MyListContainer:
def __init__(self):
self.list = []
def purge(self):
for object in self.list:
if (object.my_cond()):
self.list.remove(object)
return self.list
container = MyListContainer()
# now suppose both obj.my_cond() return True
obj1 = MyCustomObject(par)
obj2 = MyCustomObject(other_par)
container.list = [obj1, obj2]
# returning not an empty list but [obj2]
container.purge()
It doesn't work as I expected because when the cycle in "purge" delete the first object in list the second one is shifted to the beginning of the list and the cycle is ended.
I solved duplicating self.list before the for cycle:
...
local_list = self.list[:]
for object in local_list:
...
I suppose that the for statement stop working because I'm changing the length of the original list. Can someone clarify this point ?
And is there a more "elegant" way to solve this problem ? If I have more than few elements inside the list, duplicating it every time does not seem a good idea.
Maybe the filter() function is the right one but i whish to have some other approach if any.
I'm a newbie.
To summarize your useful answers:
- Never edit a list you are looping
- Duplicate the list or use list comprehensions
- Duplicating a list could not waste your memory or in this case who's mind about it
回答1:
Filter (or list comprehension) IS the way to go. If you want to do it inplace, something like this would work:
purge = []
for i,object in enumerate(self.list):
if object.mycond()
purge.append(i)
for i in reversed(purge):
del self.list[i]
Or alternatively, the purge list can be made with a comprehension, a shortcut version looks like:
for i in reversed([ i for (i,o) in enumerate(self.list) if o.mycond() ]):
del self.list[i]
回答2:
Don't try. Just don't. Make a copy or generate a new list.
回答3:
Just make yourself a new list:
def purge(self):
self.list = [object for object in self.list if not object.my_cond()]
return self.list
Reserve any optimization until you've profiled and found that this method really is the bottleneck of your application. (I bet it won't be.)
回答4:
In python variables are actually labels to data. Duplicating a list is, for the most part, making a new set of pointers to the data from the first list. Don't feel too bad about it.
List comprehensions are your friend.
e.g.
>>> a = range(20)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> [ x for x in a if x % 2 == 0 ]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
回答5:
Your second solution in which you duplicate the list is the right way to go. Afterward you can just replace the old list with the duplicate if need be.
回答6:
It's perfectly safe to shorten the list in place if you do it in reverse!
>>> a=range(20)
>>> for i in reversed(range(len(a))):
... if a[i]%2: del a[i]
...
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Another way is to reassign the whole slice
>>> a=range(20)
>>> a[:]=(x for x in a if not x%2)
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
If the items in the list are unique, this works too
>>> a=range(20)
>>> for item in reversed(a):
... if item%2: a.remove(item)
...
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Here is some more explanation in response to yuri's comment
Suppose we have
>>> a=[0,1,2,3,4,5]
Now trying naively to delete the 3rd and 4th items
>>> del a[3]
>>> del a[4]
>>> a
[0, 1, 2, 4] # didn't work because the position of all the item with index >=3 was changed
However if we do the del
's in the opposite order
>>> a=[0,1,2,3,4,5]
>>> del a[4]
>>> del a[3]
>>> a
[0, 1, 2, 5] # this is the desired result
Now extend that idea over a for loop with a removal condition, and you see that removal from the live list is possible
回答7:
indeces = []
minus = 0
for i in range(self.list):
if cond(self.list[i]):
indeces.append(i)
for i in indeces:
self.list = self.list[:(i-minus)].extend(self.list[i-minus+1:])
来源:https://stackoverflow.com/questions/2233388/how-to-delete-list-elements-while-cycling-the-list-itself-without-duplicate-it