为什么list.remove()的行为令人意外?

19 投票
3 回答
24647 浏览
提问于 2025-04-17 00:25
from pprint import *

sites = [['a','b','c'],['d','e','f'],[1,2,3]]

pprint(sites)

for site in sites:
        sites.remove(site)

pprint(sites)

输出结果:

[['a', 'b', 'c'], ['d', 'e', 'f'], [1, 2, 3]]
[['d', 'e', 'f']]

为什么它不是 None,或者一个空列表 [] 呢?

3 个回答

3

通常情况下,我会认为迭代器会因为修改了连接的列表而停止。不过在字典的情况下,这种情况至少会发生。

那么,为什么d、e、f这些东西没有被移除呢?我只能猜测:可能迭代器内部有一个计数器(或者它甚至只是基于“备用迭代协议”,也就是用getitem来实现)。

也就是说,第一个返回的项目是sites[0],也就是['a', 'b', 'c']。这个项目随后被从列表中移除。

第二个是sites[1],它是[1, 2, 3],因为索引已经改变了。这个也被移除了。

第三个应该是sites[2],但是由于这会导致索引错误,迭代器就停止了。

12

在Python中,一边遍历一个集合一边改变它的大小,就像在C和C++中做一些不明智的事情一样,可能会导致一些意想不到的错误。你可能会遇到异常,或者行为会变得很奇怪。总之,不要这样做。在这个特定的情况下,背后发生的事情大概是这样的:

  • 迭代器从索引0开始,记住自己在索引0,并给你返回那个位置的项目。
  • 你把索引0的项目删除了,之后的所有项目都向左移动了一位,以填补这个空缺。
  • 当迭代器被要求获取下一个项目时,它会把当前的索引加1,记住新的索引是1,然后给你返回这个位置的项目。但是因为刚才删除项目的操作,索引1现在其实是原本在索引2的项目(也就是最后一个项目)。
  • 你把这个项目也删除了。
  • 当迭代器被要求获取下一个项目时,它会发现下一个索引(2)已经超出了范围(现在的范围是0到0)。
54

这是因为你在遍历一个列表的时候,同时还在修改它。这样做是不对的。

对于这种情况,你应该先复制一份列表,然后再对复制的那份进行遍历。

for site in sites[:]:
    sites.remove(site)

撰写回答