2

我在这个 Python for 语句中浪费了一点时间:

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()

它不像我预期的那样工作,因为当“清除”中的循环删除列表中的第一个对象时,第二个对象被转移到列表的开头并且循环结束。

我在 for 循环之前解决了复制 self.list 的问题:

...
local_list = self.list[:]
for object in local_list:
...

我想 for 语句停止工作是因为我正在更改原始列表的长度。有人可以澄清这一点吗?

有没有更“优雅”的方法来解决这个问题?如果列表中有多个元素,那么每次都复制它似乎不是一个好主意。

也许 filter() 函数是正确的,但如果有的话,我希望有其他方法。

我是新手。


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
4

7 回答 7

5

Don't try. Just don't. Make a copy or generate a new list.

于 2010-02-09T23:45:03.243 回答
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.)

于 2010-02-09T23:45:41.127 回答
2

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]
于 2010-02-09T23:53:06.550 回答
2

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]
于 2010-02-09T23:54:11.143 回答
0

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.

于 2010-02-09T23:48:57.657 回答
0

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

于 2010-02-10T00:13:18.383 回答
-1
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:])
于 2010-02-10T01:31:16.307 回答