作为一项实验,我这样做了:
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']
>>>
对此有何解释?如何重新编写以删除每个项目?
答案 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 computer和making 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)
删除第一次出现,然后检查序列中的下一个数字。由于序列已经改变,它需要下一个奇数,依此类推......
答案 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
`