对列表for循环中remove列表中元素异常问题
问题
假设现在存在以下列表:
server_list = [
{'ip': '10.0.0.1', 'role': 'proxy'},
{'ip': '10.0.0.2', 'role': 'proxy'},
{'ip': '10.0.0.1', 'role': 'app'},
{'ip': '10.0.0.2', 'role': 'app'},
{'ip': '10.0.0.3', 'role': 'db'}
]
在某次批量操作中,需要对列表按照 ip
字段进行去重,以避免同一台机器重复操作。
简单的写了一下代码逻辑:
_server = []
for ele in server_list:
_ip = ele.get('ip')
if _ip:
if _ip in _server:
server_list.remove(ele)
else:
_server.append(_ip)
print len(server_list)
按照理解,这里执行完最后的输出应该只有 3 台机器,但实际执行结果却是 4
台机器。
分析
重新修改了代码,在 for 循环中打印每次执行的 ip 的值:
_server = []
for ele in server_list:
_ip = ele.get('ip')
print _ip # add this line
if _ip:
if _ip in _server:
server_list.remove(ele)
else:
_server.append(_ip)
最后执行结果如下:
10.0.0.1
10.0.0.2
10.0.0.1
10.0.0.3
发现实际列表有 5 个元素,但是 for 循环只执行了 4 次。
唯一一个比较可能出问题的点就是对列表的删除元素操作,尝试去掉删除操作,重新执行。
结果是输出了 5 个 ip,也就是正常的循环次数,所以问题出现在对列表删除操作中。
原因是不能在同一个列表 for
循环中对该列表进行删除元素操作。
然而这个问题在使用的 python2.7 版本中没有异常提示。容易出现认知差异如下:
之前的认知是,在列表的 for
循环开始时就已经处理所有需要循环的数据,
所以认为在循环中对列表的操作不会影响到循环的数据。
然而实际上,for
循环是实例获取列表长度通过索引下标取出本次循环需要处理的数据,
所以当删除列表中的元素时,列表的长度会发生了变化,for 循环就会受到影响。
解决
在理解清楚 for
循环的处理逻辑后,解决起来就很简单了。以下列出一些常规的方式:
利用 copy 数组循环
_server = []
_server_list_copy = list(server_list)
for ele in _server_list_copy:
_ip = ele.get('ip')
if _ip:
if _ip in _server:
server_list.remove(ele)
else:
_server.append(_ip)
也就是利用一个 copy
的数据进行循环,然后再循环中删除目标数据的元素,这样就不会循环数据。
需要注意的是,copy 数据不能简单的直接赋值,至少需要 shallow copy,
如果数组的元素存在嵌套情况,建议使用 deep copy,以保证两个数据完全不会干扰。
利用dict去重再赋值
_ip_server_dic = {}
for ele in server_list:
_ip = ele.get('ip')
if _ip:
_ip_server_dic[_ip] = ele
if _ip_server_dic:
server_list = _ip_server_dic.values()
因为需要关注的只是 ip 信息,其他信息比如 role
字段没有要求,
所以直接将需要去重字段的值作为key,原数据作为 value 创建一个字典。
最后再将字典的所有 value 取出来就可以拿到按 ip 字段去重后的列表。
参考