在Python中使用enumerate迭代列表时,我应该创建一个副本吗



在回答这个问题时,我遇到了一些在Python中从未想过的东西(由用户指出(。

基本上,我已经知道(这里有一个有趣的线程(,为了避免奇怪的行为,我必须在Python中对列表进行迭代时进行复制。

现在,我的问题是,使用enumerate能克服这个问题吗?

test_list = [1,2,3,4]
for index,item in enumerate(test_list):
if item == 1:
test_list.pop(index)

这个代码是安全的还是我应该使用,

for index,item in enumerate(test_list[:]):

首先,让我们回答您的直接问题:

enumerate在这里没有任何帮助。它的工作方式就好像它把迭代器放在底层的可迭代对象上一样(至少在CPython中,这正是它所做的(,所以任何对列表迭代器不合法或不安全的事情,对围绕该列表迭代程序的枚举对象都不合法或安全。


您的原始用例——设置test_list[index] = new_value——在实践中是安全的——但我不确定它是否能保证是安全的。

您的新用例——调用test_list.pop(index)——可能不安全。


列表迭代器最明显的实现基本上只是对列表的引用和对该列表的索引。因此,如果在当前位置或该位置的左侧插入或删除,则肯定会破坏迭代器。例如,如果删除lst[i],则会将所有内容从i + 1移到最后一个位置,因此当转到i + 1时,会跳过原始的第i + 1个值,因为它现在是第i个值。但如果在当前位置的右侧插入或删除,那就没问题。

由于test_list.pop(index)在当前位置的左侧或左侧进行删除,因此即使使用此实现也不安全。(当然,如果你仔细编写了算法,这样在命中后跳过值就无关紧要了,也许这也没关系。但更多的算法无法处理这一问题。(

可以想象,Python实现可以存储一个指向列表存储所用数组中当前位置的原始指针。这意味着在的任何位置插入可能会破坏迭代器,因为插入可能会导致整个列表被重新分配到新内存。如果实现有时会在缩小列表时重新分配列表,那么删除任何位置都可以。我不认为Python不允许实现所有这些,所以如果你想偏执,在迭代时永远不要插入或删除可能会更安全。

如果你只是替换一个现有的值,很难想象在任何合理的实现下,这会破坏迭代器。但是,据我所知,语言引用和list库引用1实际上并没有对列表迭代器的实现做出任何承诺2

因此,这取决于你是否关心"我的实施安全"、"迄今为止每一次书面实施都是安全的"、"每一次可以想象的(对我来说(实施都安全"或"通过引用保证安全"。

我认为大多数人都很乐意在迭代过程中替换列表项,但避免缩小或增加列表。然而,肯定有生产代码,至少会删除迭代器右侧的代码。


1。我相信教程只是说在迭代时永远不要修改任何数据结构——但这就是教程。总是遵循这条规则当然是安全的,但遵循不那么严格的规则也可能是安全的

除了如果key函数或其他任何东西试图在sort中间以任何方式访问列表之外,结果是未定义的

由于是我的评论导致了这一点,我将添加我的后续:

enumerate可以被认为是一个生成器,因此它只需要一个序列(实际上是任何序列或迭代器(,并"生成"一个递增索引,该索引将与传递的序列中的每个项一起生成(因此,它不会仅仅使用"enumerate对象"来复制或更改列表(。

在这个问题中的代码的情况下,您从来没有改变要迭代的列表的长度,一旦运行了if语句,元素的值就无关紧要了。因此,副本是不需要的,当元素被删除时,它将是需要的,因为迭代器索引与列表共享,并且不考虑被删除的元素。

Python Ninja有一个很好的例子,说明何时应该使用副本(或移动到列表理解(

最新更新