从列表中删除项目 - 在迭代过程中 - 这个成语有什么问题?[重复]
- 2025-02-08 08:52:00
- admin 原创
- 46
问题描述:
作为一个实验,我做了以下事情:
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']
>>>
这该如何解释?如何重写该代码以删除所有项目?
解决方案 1:
有些答案解释了为什么会发生这种情况,有些则解释了你应该做什么。我会毫不犹豫地把这些碎片拼凑起来。
这是什么原因呢?
因为 Python 语言的设计目的不同,它处理这种用例的方式也不同。文档中说得很清楚:
在循环中修改正在迭代的序列是不安全的(这只可能发生在可变序列类型中,例如列表)。如果您需要修改正在迭代的列表(例如,复制选定的项目),则必须迭代副本。
重点是我。请参阅链接页面了解更多信息 —— 本文档受版权保护,保留所有权利。
您可能很容易理解为什么会得到这样的结果,但这基本上是一种未定义的行为,并且很容易在每次构建过程中发生改变,且没有任何警告。千万不要这样做。
这就像想知道为什么i += i++ + ++i
那一行代码会在你的架构上、在你为你的语言构建的编译器上产生这样的效果——包括但不限于破坏你的电脑和让恶魔从你的鼻子里飞出来:)
如何重写它以删除每个项目?
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)]
解决方案 2:
您不能同时迭代列表并对其进行变异,而是迭代切片:
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 = []
解决方案 3:
您不能修改正在迭代的列表,否则会得到这种奇怪的结果。为此,您必须迭代列表的副本:
for i in letters[:]:
letters.remove(i)
解决方案 4:
它删除第一个出现的位置,然后检查序列中的下一个数字。由于序列已经改变,因此它取下一个奇数,依此类推...
取“a”
删除“a” -> 第一个项目现在是“b”
采取下一个项目“c”-...
解决方案 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']
解决方案 6:
python 可能使用指针,删除从前面开始。第二行中的变量“letters”与第三行中的变量“letters”部分值不同。当 i 为 1 时,a 被删除,当 i 为 2 时,b 已移动到位置 1,c 被删除。您可以尝试使用“while”。
解决方案 7:
#!/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!"
我认为这更好地解释了这个问题,上面的代码块有效,而下面的代码块无效。
“保留”在底部列表中的项目永远不会被打印出来,因为您正在修改正在迭代的列表,这是一个灾难的根源。
解决方案 8:
好吧,我来晚了,但我一直在思考这个问题,在看了 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个项并从序列中删除该项时,后续列表元素与其索引之间的对应关系会发生变化。就迭代器而言,前 (n+1)个项变为第 n个项。换句话说,迭代器现在认为序列中的“下一个”项实际上是“当前”项。
因此,当被要求给出下一个项目时,它将给出前 (n+2)个项目(即新的 (n+1)个项目)。
因此,对于上述代码,迭代器next()
方法将仅给出原始列表中的 n+0、n+2、n+4、... 个元素。n+1、n+3、n+5、... 个项目永远不会暴露给该remove
语句。
尽管所讨论代码的预期活动很明确(至少对于人来说),但迭代器可能需要更多的自省来监视其迭代序列的变化,然后以“人性化”的方式行事。
如果迭代器可以返回序列的先前或当前元素,则可能存在通用的解决方法,但事实上,您需要迭代列表的副本,并确保在迭代器到达它们之前不要删除任何项目。
解决方案 9:
最初 i
是引用,当循环运行时,第一个位置元素被删除或移除,而第二个位置元素占据第一个位置,但指针移动到第二个位置,这种情况继续下去,这就是我们无法删除的原因b,d,f,h,j,l
`