在迭代时从列表中移除项目 - 这个习惯用法有什么问题?

30 投票
9 回答
24196 浏览
提问于 2025-04-15 23:05

作为一个实验,我做了这个:

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 个回答

8

你不能在遍历一个列表的时候去修改它,否则你会得到一些奇怪的结果。要做到这一点,你需要遍历这个列表的一个副本:

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

你不能在遍历一个列表的同时修改它,应该遍历它的一个切片:

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 = []
48

有些回答解释了为什么会这样,有些则告诉你应该怎么做。我来把这些信息整理一下。


为什么会这样呢?

因为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)]

撰写回答