在 Python 中进行迭代时修改可迭代对象_tiknovel-最新最全的nft,web3,AI技术资讯技术社区

在 Python 中进行迭代时修改可迭代对象

2022-03-26 20:56:32  浏览:372  作者:管理员
在 Python 中进行迭代时修改可迭代对象

如果您尝试在遍历序列时对其进行变异,Python 通常不会抱怨。例如:

# src.pyl = [3, 4, 56, 7, 10, 9, 6, 5]for i in l:
    if not i % 2 == 0:
        continue
    l.remove(i)print(l)

上面的代码片段遍历一个数字列表l并就地修改列表以删除任何偶数。但是,运行脚本会打印出以下内容:

[3, 56, 7, 9, 5]

等一下!输出看起来不正确。最终列表仍然包含56偶数。为什么它被跳过了?在 for 循环前进的同时打印列表的成员会显示里面发生了什么:

347106[3, 56, 7, 9, 5]

从输出来看,似乎 for 循环甚至没有访问序列的所有元素。但是,尝试模拟 for 循环内部发生的事情iter并使next情况更加清晰。请注意以下示例。我正在使用ipythonshell 来探索:

In [1]: l = [3, 4, 56, 7, 10, 9, 6, 5]In [2]: # Make the list an iterator.In [3]: it = iter(l)In [4]: # Emulate for-loop by applying 'next()' function on 'it'.In [5]: next(it)Out[5]: 3In [6]: next(it)Out[6]: 4In [7]: # Remove a value that's already been visited by the iterator.In [8]: l.remove(3)In [9]: next(it)Out[9]: 7In [10]: # Notice how the iterator skipped 56. Remove another.In [11]: l.remove(4)In [12]: next(it)Out[12]: 9

REPL 实验表明

每当您删除迭代器已经访问过的可迭代元素时,在下一次迭代中,迭代器将向右跳转 1 个元素。这可以使迭代器跳过一个值。如果在迭代器开始迭代之后为序列添加一些值,反之亦然。在这种情况下,在下一次迭代中,迭代器将向左跳转 1 个元素,并可能再次访问相同的值。

在迭代开始后添加值时会发生以下情况:

In[1]: l = [3, 4, 56, 7, 10, 9, 6, 5]In[2]: it = iter(l)In[3]: next(it)Out[3]: 3In[4]: next(it)Out[4]: 4In[5]: l.insert(0, 44)In[6]: next(it)Out[6]: 4

请注意在将值添加到列表之后,该元素4是如何被访问两次的l

解决方案

为了解决这个问题,您必须确保目标元素在迭代器已经访问它们之后不会被删除。您可以以相反的顺序迭代并删除保持原始顺序的元素。第一个片段可以重写如下:

# src.pyl = [3, 4, 56, 7, 10, 9, 6, 5]# Here, 'reversed' returns a lazy iterator, so it's performant!for i in reversed(l):
    print(i)
    if not i % 2 == 0:
        continue
    l.remove(i)print(l)

运行脚本打印:

5691075643[3, 7, 9, 5]

请注意,迭代器现在如何访问所有元素并且最终列表包含预期的奇数元素。

解决这个问题的另一种方法是——l在迭代之前复制列表。但如果l很大,这可能会很昂贵:

# src.pyl = [3, 4, 56, 7, 10, 9, 6, 5]# Here 'l.copy()' creates a shallow copy of 'l'. It's# less performant than 'reversed(l)'.for i in l.copy():
    print(i)
    if not i % 2 == 0:
        continue
    l.remove(i)print(l)

这一次,迭代和删除元素的顺序是相同的,但这不是问题,因为这两个操作发生在两个不同的列表上。运行代码段会产生以下输出:

3456710965[3, 7, 9, 5]

字典呢

字典甚至不允许您在迭代时更改它们的大小。以下代码段引发了一个RuntimeError

# src.py# {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}d = {k: k for k in range(10)}for k, v in d.items():
    if not v % 2 == 0:
        continue
    d.pop(k)
Traceback (most recent call last):  File "/home/rednafi/canvas/personal/reflections/src.py", line 4, in <module>
    for k,v in d.items():RuntimeError: dictionary changed size during iteration

您可以通过复制字典的键并在从字典中删除元素的同时迭代它来解决此问题。这是一种方法:

# src.py# {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}d = {k: k for k in range(10)}# This creates a copy of all the keys of 'd'.# At least we arent't creating a new copy of the# entire dict and tuple creation is quite fast.for k in tuple(d.keys()):
    if not d[k] % 2 == 0:
        continue
    d.pop(k)print(d)

运行代码段打印:

{1: 1, 3: 3, 5: 5, 7: 7, 9: 9}

瞧,偶数的键值对已成功移除!


评论区

共 0 条评论
  • 这篇文章还没有收到评论,赶紧来抢沙发吧~

【随机内容】

返回顶部