在迭代时从列表中移除项目 - 这个习惯用法有什么问题?
作为一个实验,我做了这个:
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)]