Python中遍历字典过程中更改元素导致异常的解决方法
先来回顾一下Python中遍历字典的一些基本方法:
脚本:
#!/usr/bin/python
dict={"a":"apple","b":"banana","o":"orange"}
print"##########dict######################"
foriindict:
print"dict[%s]="%i,dict[i]
print"###########items#####################"
for(k,v)indict.items():
print"dict[%s]="%k,v
print"###########iteritems#################"
fork,vindict.iteritems():
print"dict[%s]="%k,v
print"###########iterkeys,itervalues#######"
fork,vinzip(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的话,就删掉
>>>forkind:
...ifd[k]==0:
...del(d[k])
...
Traceback(mostrecentcalllast):
File"<stdin>",line1,in<module>
RuntimeError:dictionarychangedsizeduringiteration
#结果抛出异常了,两个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常量。
>>>forkind.keys():
...ifd[k]==0:
...del(d[k])
...
>>>d
{'a':1,'c':1}
#结果也是对的
>>>
#这里初始化一个dict
>>>d={'a':1,'b':0,'c':1,'d':0}
#本意是遍历dict,发现元素的值是0的话,就删掉
>>>forkind:
...ifd[k]==0:
...del(d[k])
...
Traceback(mostrecentcalllast):
File"<stdin>",line1,in<module>
RuntimeError:dictionarychangedsizeduringiteration
#结果抛出异常了,两个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常量。
>>>forkind.keys():
...ifd[k]==0:
...del(d[k])
...
>>>d
{'a':1,'c':1}
#结果也是对的
>>>
其实这个问题本来很简单,就是说如果遍历一个字典,但是在遍历中改变了他,比如增删某个元素,就会导致遍历退出,并且抛出一个dictionarychangedsizeduringiteration的异常.
解决方法是遍历字典键值,以字典键值为依据遍历,这样改变了value以后不会影响遍历继续。
但是下面又有一位大神抛出高论:
首先,python是推荐使用迭代器的,也就是forkinadict形式。其次,在遍历中删除容器中的元素,在C++STL和Python等库中,都是不推荐的,因为这种情况往往说明了你的设计方案有问题,所有都有特殊要求,对应到python中,就是要使用adict.key()做一个拷贝。最后,所有的Python容器都不承诺线程安全,你要多线程做这件事,本身就必须得加锁,这也说明了业务代码设计有问题的.
但由“遍历中删除特定元素”这种特例,得出“遍历dict的时候,养成使用forkind.keys()的习惯”,我觉得有必要纠正一下。在普通的遍历中,应该使用forkinadict。
另外,对于“遍历中删除元素”这种需求,pythonic的做法是adict={k,vforadict.iteritems()ifv!=0}或alist=[iforiinalistifi!=0]
这个写法让我眼前一亮:怎么还有这个语法?
再仔细一看,他可能是这个意思:
#!/usr/bin/envpython
#-*-coding=utf-8-*-
a={'a':1,'b':0,'c':1,'d':0}
b={}
fork,vina.items():
ifv!=0:
b.update({k:v})
adict=b
delb
printa
#!/usr/bin/envpython
#-*-coding=utf-8-*-
a={'a':1,'b':0,'c':1,'d':0}
b={}
fork,vina.items():
ifv!=0:
b.update({k:v})
adict=b
delb
printa
不知道对不对。
因为这个写法一开始让我猛然想到三元操作符,仔细一看才发现不是,以前Goolge到有个解决方案
val=float(raw_input("Age:"))
status=("working","retired")[val>65]
print"Youshouldbe",status
val=float(raw_input("Age:"))
status=("working","retired")[val>65]
print"Youshouldbe",status
val>65是个逻辑表达式,返回0或者1,刚好作为前面那个元组的ID来取值,实在是太妙了。。。
不过在Google的资料里面还有一个版本
#V1ifXelseV2 s=None a="notnull"ifs==Noneelses printa #'notnull'
后来发帖在华蟒用户组(中文Python技术邮件列表)中提到后众多大神解答如下:
>>>alist=[1,2,0,3,0,4,5]
>>>alist=[iforiinalistifi!=0]
>>>alist
[1,2,3,4,5]
>>>d={'a':1,'b':0,'c':1,'d':0}
>>>d=dict([(k,v)fork,vind.iteritems()ifv!=0])
>>>d
{'a':1,'c':1'}
如果大于Python>=2.7
还可以用这个写法:
>>>d={k:vfork,vind.iteritems()ifv!=0}