遍历列表的同时追加元素

使用for循环遍历一个python列表的同时,使用append方法向列表中追加元素,这个for循环还能正常执行么?在元素pip源码时,我在legacy_resolve.py 脚本中注意到了resolve这个函数,函数定义如下

def resolve(self, requirement_set):
    root_reqs = (
            requirement_set.unnamed_requirements +
            list(requirement_set.requirements.values())
    )

    discovered_reqs = []  # type: List[InstallRequirement]
    hash_errors = HashErrors()

    for req in chain(root_reqs, discovered_reqs):
        try:
            discovered_reqs.extend(self._resolve_one(requirement_set, req))
        except HashError as exc:
            exc.req = req
            hash_errors.append(exc)

    if hash_errors:
        raise hash_errors

我们不去关注函数的具体功能,注意看discovered_reqs.extend(self._resolve_one(requirement_set, req)) 这样代码,在使用for循环遍历discovered_reqs的同时使用append方法向列表追加元素,难道不会造成什么异常么?

这是一个非常有趣的问题,在讨论这个问题之前,先来看看如果使用remove方法从列表里删除一个元素会怎样

lst = [1, 3, 2, 5, 7, 9]

for i in lst:
    if i % 2 == 1:
        lst.remove(i)

print(lst)

这段代码的意图是删除列表里的奇数,然而lst最终的内容却是 [3, 2, 7],产生这个现象的原因是for循环的本质是使用迭代器进行遍历,删除迭代器刚刚访问过的元素,下一次迭代中,迭代器将向后跳转1个元素。删除元素1时,迭代器向右跳转1个元素,刚好越过了3,remove影响了迭代器的正常使用。

如果在遍历列表时使用append向列表中添加元素,会是怎样的效果呢,这里的关键是append方法是否会影响到for循环所使用的迭代器,如果append方法向列表追加元素后同时修改迭代器且不破坏迭代器,那么就不会影响迭代器的行为,下面是一段实验代码

lst = [1, 2, 3]

for item in lst:
    if item % 2 == 0:
        print(item)
        lst.append(item * 2)

    if item > 20:
        break
  • 遍历列表时,如果遇到了一个偶数,则使用append方法向列表的末尾追加一个整数
  • 当遍历到的元素大于20时,终止循环,避免无限循环

实际的输出结果是

2 4 8 16 32

从程序输出的结果进行推理,append方法改变了迭代器,for循环遍历到整数2时,向列表末尾追加了整数4, 遍历整数3结束后,仍然进入下一次循环,这说明for循环所使用的迭代器可以识别到列表中新加入的元素。

换一种方法来验证上面的推理

lst = [1, 2, 3]

tmp_iter = iter(lst)
print(next(tmp_iter))
print(next(tmp_iter))
print(next(tmp_iter))
lst.append(4)
print(next(tmp_iter))
  • iter函数返回列表的迭代器
  • next函数每次从迭代器里返回下一个元素
  • 调用了三次next函数后,迭代器已经用完,但我使用append方法向列表末尾追加了一个元素,第四次使用next函数,仍然可以正确调用并返回整数4

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案