Python字典循环RuntimeError报错分析

in 互联网技术 with 0 comment  访问: 4,303 次

TraversalDictError.png
情况如上所示,当运行程序的时候,报错内容为:RuntimeError: dictionary changed size during iteration

分析

我们知道Python字典是用哈希表(hash table)实现的。哈希表是一个数组,它的索引是对键运用哈希函数(hash function)求得的。for cn_id in cn_map_info:这种方式是通过iterator遍历字典,但是在遍历中改变了他,比如增删某个元素,就会导致遍历退出,并且抛出dictionary changed size during iteration的异常。

在我们平常使用中我们知道Python是推荐使用迭代器的,也就是for k in xdict形式。其次,在遍历中删除容器中的元素,在C++ STL 和 Python等库中,都是不推荐的,因为这种情况往往说明了你的设计方案有问题,所有都有特殊要求,对应到Python中,就是要使用xdict.key()做一个拷贝。最后,所有的Python容器都不承诺线程安全,你要多线程做这件事,本身就必须得加锁,这也说明了业务代码设计有问题的。

但由"遍历中删除特定元素"这种特例,得出"遍历dict的时候,养成使用for k in d.keys()的习惯",我觉得有必要纠正一下,在普通的遍历中,我们还是应该使用for k in xdict高效Pythonic的方法。

另外,对于"遍历中删除元素"这种需求,Pythonic的做法是 xdict = {k, v for adict.iteritems() if v != 0}alist = [i for i in alist if i != 0]

Python字典实现原理:https://harveyqing.gitbooks.io/python-read-and-write/content/python_advance/python_dict_implementation.html

解决方法

解决方法是在遍历字典键值,以字典键值为依据遍历,这样改变了value以后不会影响遍历继续。

Python2解决如下:

for cn_id in cn_map_info.keys():
    if cn_id not in monitor_id_list:
        del(cn_map_info[cn_id])

for cn_id in cn_map_info.keys()这种方式是通过一个列表来依次获取每个key(cn_map_info.keys()返回的列表),所以遍历过程不会奔溃。

Python3解决如下:

for cn_id in list(cn_map_info.keys()):
    if cn_id not in monitor_id_list:
        del(cn_map_info[cn_id])

同样Python3下也是通过列表来循环所有key,跟原字典不冲突,所以遍历不会奔溃。

为什么Python3下需要需要list()操作,分析如下:

nock:lab nock$ python2.6
Python 2.6.9 (unknown, Jul 14 2015, 19:46:31) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> monitor_id_list = [12, 13, 14, 16, 19, 20]
>>> cn_map_info = {12: 'taiwan', 13: 'hongkong', 15: 'guizhou'}
>>> print(cn_map_info.keys())
[12, 13, 15]
>>> exit()

nock:lab nock$ python3
Python 3.5.1 (default, Dec 26 2015, 18:08:53) 
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> monitor_id_list = [12, 13, 14, 16, 19, 20]
>>> cn_map_info = {12: 'taiwan', 13: 'hongkong', 15: 'guizhou'}
>>> print(cn_map_info.keys())
dict_keys([12, 13, 15])
>>> print(list(cn_map_info.keys()))
[12, 13, 15]

问题很简单明了Python2下xdict.keys()直接返回的就是列表,而Python3下xdict.keys()返回的是字典keys对象。

WeZan