首页 > 解决方案 > 在 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[:]):

标签: pythonlistiteration

解决方案


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

enumerate在这里没有任何帮助。它的工作方式就好像它持有一个指向底层可迭代对象的迭代器(并且,至少在 CPython 中,这正是它所做的),因此任何与列表迭代器不合法或不安全的事情都是不合法或不安全的使用围绕该列表迭代器的枚举对象。


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

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


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

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

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

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

因此,您是否关心“我的实现中的安全”、“每个迄今为止编写的每个实现中的安全”、“每个可能的(对我而言)实现中的安全”或“参考保证安全”都取决于您。

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


1. 我相信教程只是在某处说在迭代任何数据结构时永远不要修改它——但这就是教程。始终遵循该规则当然是安全的,但遵循不太严格的规则也可能是安全的。

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


推荐阅读