Python中遍历字典过程中更改元素导致异常的解决方法

时间:2021-05-22

先来回顾一下Python中遍历字典的一些基本方法:
脚本:

#!/usr/bin/python dict={"a":"apple","b":"banana","o":"orange"} print "##########dict######################" for i in dict: print "dict[%s]=" % i,dict[i] print "###########items#####################" for (k,v) in dict.items(): print "dict[%s]=" % k,v print "###########iteritems#################" for k,v in dict.iteritems(): print "dict[%s]=" % k,v print "###########iterkeys,itervalues#######" for k,v in zip(dict.iterkeys(),dict.itervalues()): print "dict[%s]=" % k,v

执行结果:

##########dict###################### dict[a]= apple dict[b]= banana dict[o]= orange ###########items##################### dict[a]= apple dict[b]= banana dict[o]= orange ###########iteritems################# dict[a]= apple dict[b]= banana dict[o]= orange ###########iterkeys,itervalues####### dict[a]= apple dict[b]= banana dict[o]= orange

嗯,然后我们进入“正题”--

一段关于Python字典遍历的“争论”....
先摘抄下:

#这里初始化一个dict>>> d = {'a':1, 'b':0, 'c':1, 'd':0}#本意是遍历dict,发现元素的值是0的话,就删掉>>> for k in d:... if d[k] == 0:... del(d[k])...Traceback (most recent call last): File "<stdin>", line 1, in <module>RuntimeError: dictionary changed size during iteration#结果抛出异常了,两个0的元素,也只删掉一个。>>> d{'a': 1, 'c': 1, 'd': 0}>>> d = {'a':1, 'b':0, 'c':1, 'd':0}#d.keys() 是一个下标的数组>>> d.keys()['a', 'c', 'b', 'd']#这样遍历,就没问题了,因为其实其实这里遍历的是d.keys()这个list常量。>>> for k in d.keys():... if d[k] == 0:... del(d[k])...>>> d{'a': 1, 'c': 1}#结果也是对的>>>#这里初始化一个dict>>> d = {'a':1, 'b':0, 'c':1, 'd':0}#本意是遍历dict,发现元素的值是0的话,就删掉>>> for k in d:... if d[k] == 0:... del(d[k])...Traceback (most recent call last): File "<stdin>", line 1, in <module>RuntimeError: dictionary changed size during iteration#结果抛出异常了,两个0的元素,也只删掉一个。>>> d{'a': 1, 'c': 1, 'd': 0} >>> d = {'a':1, 'b':0, 'c':1, 'd':0}#d.keys() 是一个下标的数组>>> d.keys()['a', 'c', 'b', 'd']#这样遍历,就没问题了,因为其实其实这里遍历的是d.keys()这个list常量。>>> for k in d.keys():... if d[k] == 0:... del(d[k])...>>> d{'a': 1, 'c': 1}#结果也是对的>>>

其实这个问题本来很简单,就是说如果遍历一个字典,但是在遍历中改变了他,比如增删某个元素,就会导致遍历退出,并且抛出一个dictionary changed size during iteration的异常.
解决方法是遍历字典键值,以字典键值为依据遍历,这样改变了value以后不会影响遍历继续。
但是下面又有一位大神抛出高论:

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

但由“遍历中删除特定元素”这种特例,得出“遍历dict的时候,养成使用 for k in d.keys() 的习惯”,我觉得有必要纠正一下。在普通的遍历中,应该使用 for k in adict。
另外,对于“遍历中删除元素”这种需求,pythonic 的做法是 adict = {k, v for adict.iteritems() if v != 0} 或 alist = [i for i in alist if i != 0]

这个写法让我眼前一亮:怎么还有这个语法?
再仔细一看,他可能是这个意思:

#!/usr/bin/env python# -*- coding=utf-8 -*-a = {'a':1, 'b':0, 'c':1, 'd':0}b={}for k,v in a.items(): if v != 0: b.update({k:v})adict = bdel bprint a#!/usr/bin/env python# -*- coding=utf-8 -*-a = {'a':1, 'b':0, 'c':1, 'd':0}b={}for k,v in a.items(): if v != 0: b.update({k:v})adict = bdel bprint a

不知道对不对。
因为这个写法一开始让我猛然想到三元操作符,仔细一看才发现不是,以前Goolge到有个解决方案

val = float(raw_input("Age: "))status = ("working","retired")[val>65]print "You should be",statusval = float(raw_input("Age: "))status = ("working","retired")[val>65]print "You should be",status

val>65是个逻辑表达式,返回0或者1,刚好作为前面那个元组的ID来取值,实在是太妙了。。。
不过在Google的资料里面还有一个版本

#V1 if X else V2s = Nonea = "not null" if s == None else sprint a#'not null'

后来发帖在华蟒用户组(中文Python技术邮件列表)中提到后众多大神解答如下:

>>> alist = [1,2,0,3,0,4,5]>>> alist = [i for i in alist if i != 0]>>> alist[1, 2, 3, 4, 5]>>> d = {'a':1, 'b':0, 'c':1, 'd':0}>>> d = dict([(k,v) for k,v in d.iteritems() if v!=0])>>> d{'a':1,'c':1'}

如果大于Python>=2.7
还可以用这个写法:

>>> d = {k:v for k,v in d.iteritems() if v !=0 }

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章