Modifying list while iterating
1 2 3 4 5
| l = range(100)
for i in l:
print i,
print l.pop(0),
print l.pop(0) |
上面的python代码给出的输出与预期的非常不同。我想循环项目,以便在循环时跳过项目。
请解释一下。
- 无法通过查看代码来判断您希望实现什么。
- "与预期不同"。真的?你期待什么?
永远不要更改正在循环的容器,因为该容器上的迭代器不会被通知您的更改,而且,正如您注意到的,很可能会产生一个非常不同的循环和/或不正确的循环。在正常情况下,在容器副本上循环有帮助,但在您的情况下,很明显您不希望这样做,因为在循环的50段之后,容器将是空的,如果您再次尝试弹出,您将得到一个异常。
有什么不清楚的是,你想达到什么样的行为,如果有的话?!也许你可以用while来表达你的愿望…?
1 2 3 4 5
| i = 0
while i < len(some_list):
print i,
print some_list.pop(0),
print some_list.pop(0) |
- 等等,你应该在循环中增加i吗?
- @MAQ那是不必要的。pop实际上删除了元素。所以你一直在看元素0,然后弹出它。按预期工作。
- 亚历克斯,我发现这只适用于一些小例子,我们还应该避免这个吗?例如:>>>L=list('abcdefab')>>>对于i in l:if l.count(i)>1:l.remove(i)
- 因为该容器上的迭代器不会被通知您的更改:您能解释一下为什么吗?
- @hacks,因为容器甚至不跟踪它上面的迭代器,更不用说钩子,甚至改变方法来循环遍历每一个这样的迭代器,并且以某种方式神奇地让每个迭代器知道这些更改。这将是一个非常微妙、复杂的代码,并且检查会减慢非常频繁的操作。尝试对一个类似列表的容器进行编码,例如,当循环体选择性地删除一些容器项时,for i, x in enumerate(container):可以很好地工作,并且您将更好地了解这些问题——接下来是嵌套循环,接下来是嵌套循环,…—)
- 这意味着,一旦迭代器通过对l的评估(在op代码中)获得收益,那么对l的任何修改都不会通知迭代器。这就是for循环引用所说的。但是当我尝试运行这个代码for w in words: print w if len(w) < 6: words.remove(w) print words时,它会给出输出cat defenestrate ['window', 'defenestrate']。'window'去哪儿了?似乎迭代器知道了对words的更改。
- @haccks,words列表"向左滑动一个",每次删除,迭代器都没有得到通知,因此将其内部索引保存到words中,然后在循环的下一段中递增。因此,影响(这种未定义的行为在实际中的表现方式,在本例中)是列表中的某些项在迭代中被"跳过"。再次:在列表类上滚动您自己的迭代器,您将更好地掌握它。
- 单词表"左滑一个",每次删除,…:当然是这样。但是内部计数器/索引将跟踪迭代器的项,而不是列表项afaik。我错了吗?
- 迭代器不拥有也不保存任何项--它每次需要为next提供值(之后还增加其内部迭代器)时,都会到达列表(在迭代器的当前索引中,该索引是迭代器拥有并保存的唯一内容)。
- 那么这行的含义是什么:表达式列表被计算一次;?(实际上,我有点怪,而且还是Python的初学者。)
- @hacks,所以开始批评"评论中的扩展讨论",所以如果你想知道这一点,请打开一个新的Q——不能一直在评论中谈论这一点,我没有时间聊天室。你所引用的句子正是它所说的:表达式列表只计算一次。这并不意味着会发生任何复制--对基础列表对象的引用(它是可变的,但在循环过程中不应该改变)反映了您对该列表对象所做的任何更改,不管是对还是错。
- 好啊。然后我理解了摘录错误,这意味着只对iterable对象计算一次以在其上创建迭代器,因为for语句调用了容器对象上的iter()。这个迭代器对象使用next()逐个拉出列表项。对列表的任何修改都将影响此拉取顺序。关于迭代器的最后一条评论真的很有帮助。谢谢你的时间。
我以前被(别人的)"聪明"代码咬过,它试图在遍历列表时修改列表。我决定在任何情况下都不做这件事。
您可以使用slice操作符mylist[::3]跳过列表中的每三个项目。
1 2 3
| mylist = [i for i in range(100)]
for i in mylist[::3]:
print(i), |
号
关于我的示例的其他要点与Python3.0中的新语法有关。
- 我使用列表理解来定义mylist,因为它在python 3.0中工作(见下文)
- print是python 3.0中的一个函数
Python 3.0 range() now behaves like xrange() used to behave, except it works with values of arbitrary size. The latter no longer exists.
号
- 如果需要列表对象,那么list(range(100))比这种无效的列表理解更快、更直接。此外,for i in range(100)[::3]:也起作用。
- 另外,如果您只想迭代整数,最好还是使用range(0, 100, 3)甚至xrange(0, 100, 3)(后者在RAM中没有完整的列表)。
- @伦娜,那[范围(100)]呢?
- @Wsysuper试着把你的建议打印出来,你就会明白为什么不好了:)
- 尝试[*范围(100)]。
一般的经验法则是,在迭代集合/数组/列表时不修改它。
使用第二个列表来存储您要操作的项,并在初始循环之后在循环中执行该逻辑。
使用while循环检查数组的真实性:
1 2 3
| while array:
value = array.pop(0)
# do some calculation here |
。
它应该做到没有任何错误或滑稽的行为。
试试这个。它避免了您正在迭代的事物发生变化,这通常是一种代码味道。
1 2
| for i in xrange(0, 100, 3):
print i |
见xrange。
- python 3.0 range()现在的行为与xrange()以前的行为类似,只是它可以处理任意大小的值。后者已不复存在。
我想这就是你想要的:
1 2 3 4 5 6 7 8 9 10
| l = range(100)
index = 0
for i in l:
print i,
try:
print l.pop(index+1),
print l.pop(index+1)
except:
pass
index += 1 |
当要弹出的项目数是运行时决策时,编写代码非常方便。但是它的运行效率很差,代码很难维护。
此切片语法生成列表的副本,并执行所需操作:
1 2 3 4 5
| l = range(100)
for i in l[:]:
print i,
print l.pop(0),
print l.pop(0) |
。
- 我看得很晚,但这个答案是错误的。提供的代码在迭代50个以上的项目后会崩溃,因为它每次都会通过循环从原始列表中删除两个项目,但不会跳过切片中的任何项目。