从列表中删除项目 - 在迭代期间 - 这个成语有什么问题?

时间:2010-05-24 11:54:40

标签: python list loops

作为一项实验,我这样做了:

letters=['a','b','c','d','e','f','g','h','i','j','k','l']
for i in letters:
    letters.remove(i)
print letters

最后一次打印显示并非所有项目都被删除了? (彼此都是)。

IDLE 2.6.2      
>>> ================================ RESTART ================================
>>> 
['b', 'd', 'f', 'h', 'j', 'l']
>>> 

对此有何解释?如何重新编写以删除每个项目?

9 个答案:

答案 0 :(得分:37)

有些答案解释了为什么会发生这种情况,有些人会解释你应该做些什么。我会无耻地把碎片放在一起。


这是什么原因?

因为Python语言旨在以不同方式处理此用例。 The documentation makes it clear:

  

修改循环中迭代的序列是不安全的(这只能发生在可变序列类型中,例如列表)。 如果您需要修改要迭代的列表(例如,复制所选项目),则必须遍历副本

强调我的。有关详细信息,请参阅链接页面 - 文档受版权保护,所有权利均已保留。

你可以很容易地理解为什么你得到了你得到的东西,但它基本上是undefined behavior,可以很容易地改变而不会从构建到构建发出警告。只是不要这样做。

就像wondering why i += i++ + ++i does whatever the hell it is it that line does on your architecture on your specific build of your compiler for your language - 包括但不限于trashing your computermaking demons fly out of your nose:)


如何重新编写以删除所有项目?

  • del letters[:](如果您需要更改对此对象的所有引用)
  • letters[:] = [](如果您需要更改对此对象的所有引用)
  • letters = [](如果您只想使用新对象)

也许您只是想根据条件删除一些项目?在这种情况下,您应该迭代列表的副本。制作副本的最简单方法是使用[:]语法创建包含整个列表的切片,如下所示:

#remove unsafe commands
commands = ["ls", "cd", "rm -rf /"]
for cmd in commands[:]:
  if "rm " in cmd:
    commands.remove(cmd)

如果您的支票不是特别复杂,您可以(也可能应该)过滤:

commands = [cmd for cmd in commands if not is_malicious(cmd)]

答案 1 :(得分:9)

你不能迭代列表并同时改变它,而是迭代切片:

letters=['a','b','c','d','e','f','g','h','i','j','k','l']
for i in letters[:]: # note the [:] creates a slice
     letters.remove(i)
print letters

那就是说,对于像这样的简单操作,你应该简单地使用:

letters = []

答案 2 :(得分:7)

您无法修改正在迭代的列表,否则您将获得这种奇怪的结果类型。为此,您必须遍历列表的副本:

for i in letters[:]:
  letters.remove(i)

答案 3 :(得分:5)

你想要做的是:

letters[:] = []

del letters[:]

这将保留原始对象letters指向的内容。其他选项,如letters = [],会创建一个新对象并指向它letters:旧对象通常会在一段时间后被垃圾收集。

并非所有值都被删除的原因是您在迭代时更改列表。

ETA :如果您想从列表中过滤值,可以使用以下列表推导:

>>> letters=['a','b','c','d','e','f','g','h','i','j','k','l']
>>> [l for l in letters if ord(l) % 2]
['a', 'c', 'e', 'g', 'i', 'k']

答案 4 :(得分:5)

删除第一次出现,然后检查序列中的下一个数字。由于序列已经改变,它需要下一个奇数,依此类推......

  • 拿“a”
  • 删除“a” - >第一项现在是“b”
  • 采取下一项,“c” -...

答案 5 :(得分:1)

可能python使用指针,删除从前面开始。第二行中的变量“字母”部分地具有与第三行中的变量“字母”不同的值。当我是1然后a被删除,当我是2然后b被移动到位置1并且c被移除。您可以尝试使用“while”。

答案 6 :(得分:1)

    #!/usr/bin/env python
    import random
    a=range(10)

    while len(a):
        print a
        for i in a[:]:
            if random.random() > 0.5:
                print "removing: %d" % i
                a.remove(i)
            else:
                print "keeping: %d"  % i           

    print "done!"
    a=range(10)

    while len(a):
        print a
        for i in a:
            if random.random() > 0.5:
                print "removing: %d" % i
                a.remove(i)
            else:
                print "keeping: %d"  % i           

    print "done!"

我认为这可以更好地解释问题,顶部代码块可以工作,而底部代码则不行。

在底部列表中“保留”的项目永远不会打印出来,因为您正在修改正在迭代的列表,这是一个灾难的处方。

答案 7 :(得分:0)

好的,我在这里参加派对有点晚了,但是我一直在考虑这个问题,在看了Python的(CPython)实现代码之后,有一个我喜欢的解释。如果有人知道为什么愚蠢或错误,我会很高兴听到原因。

问题是使用迭代器在列表中移动,同时允许更改列表。

所有迭代器都必须告诉你(在这种情况下)列表中的哪个项目位于当前项目之后(即使用next()函数)。

我相信当前实现迭代器的方式,它们只跟踪它们迭代的最后一个元素的索引。查看iterobject.c可以看到似乎是迭代器的定义:

typedef struct {
    PyObject_HEAD
    Py_ssize_t it_index;
    PyObject *it_seq; /* Set to NULL when iterator is exhausted */
} seqiterobject;

其中it_seq指向要迭代的序列,it_index给出迭代器提供的最后一项的索引。

当迭代器刚刚提供了n th 项并且从序列中删除了该项时,后续列表元素与其索引之间的对应关系会发生变化。就迭代器而言,前(n + 1) st 项成为n th 项。换句话说,迭代器现在认为“下一个”是什么?序列中的项目实际上是当前的'项目

因此,当被要求提供下一个项目时,它将给出前一个(n + 2) nd 项目(即新的(n + 1) st 项目)。

因此,对于相关代码,迭代器的next()方法将仅提供原始的n + 0,n + 2,n + 4,...元素名单。 n + 1,n + 3,n + 5,...项永远不会暴露给remove语句。

尽管有问题的代码的预期活动是明确的(至少对于一个人来说),但是它可能需要更多的内省来迭代器监视它迭代的序列中的变化,然后,在&中行动#39;人'方式。

如果迭代器可以返回序列的先前元素或当前元素,则可能会有一般的解决方法,但实际上,您需要迭代列表的副本,并确保在迭代器到达之前删除任何项目。

答案 8 :(得分:0)

最初 i 是 a 的引用,因为循环运行第一个位置元素删除或移除,第二个位置元素占据第一个位置,但指针移动到第二个位置,这继续下去,这就是我们的原因无法删除 b,d,f,h,j,l

`