python编程细节──遍历dict的两种方法比较
python以其优美的语法和方便的内置数据结构,赢得了不少程序员的亲睐。
其中有个很有用的数据结构,就是字典(dict),使用非常简单。说到遍历一个dict结构,我想大多数人都会想到 for key in dictobj
的方法,确实这个方法在大多数情况下都是适用的。但是并不是完全安全,请看下面这个例子:
#这里初始化一个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的时候,养成使用 for k in d.keys()
的习惯。
不过,如果是多线程的话,这样就绝对安全吗?也不见得:当两个线程都取完d.keys()以后,如果两个线程都去删同一个key的话,先删的会成功,后删的那个肯定会报 KeyError ,这个看来只能通过其他方式来保证了。
tusooa 在 2010年09月17日 22:36 说:【 】
多线程,try一下呗。
firefoxmmx 在 2010年09月17日 22:47 说:【 】
哦。。这个问题我也遇到过。。
oceanboo 在 2010年09月18日 02:21 说:【 】
多线程如果不要效率的花,还是lock一下吧,这样最保险了。
Iven 在 2010年09月18日 08:04 说:【 】
因为 for k in d: 是迭代,所以才会这样的,我记得貌似这样写会有警告吧……
LaiYonghao 在 2010年09月18日 10:33 说:【 】
在 google reader 看到已经有两个推荐了,不得不站出来说一声,作者的这种说法非常错误。首先,python 是推荐使用迭代器的,也就是 for k in adict 形式。其次,在遍历中删除容器中的元素,在 C++ STL 和 Python 等库中,都是不推荐的,因为这种情况往往说明了你的设计方案有问题,所有都有特殊要求,对应到 python 中,就是要使用 adict.key() 做一个拷贝。最后,所有的 Python 容器都不承诺线程安全,你要多线程做这件事,本身就必须得加锁,这也说明了业务代码设计有问题的。
bones7456 在 2010年09月19日 09:12 说:【 】
我认为在遍历中删除某些符合特定条件的元素,应该是个很正常的应用吧?而且 d.keys() 的形式,也能很好地应付这种应用。
另外,线程安全确实是要自己用锁或者其他方法保障,这点我文章里也有提到的。
LaiYonghao 在 2010年09月19日 09:56 说:【 】
但由“遍历中删除特定元素”这种特例,得出“遍历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]
bones7456 在 2010年09月19日 17:02 说:【 】
好吧,我并不是反对用 for k in adict 的方式。如果自己能确认遍历期间不会增删数据的话,这个方式也是可取的。谢谢提醒~
ihipop 在 2010年10月31日 20:09 说:【 】
这个好像不是python的语法吧?伪代码?
Raphael 在 2012年01月09日 15:11 说:【 】
不是伪代码,这是Python的特点,只不过变量名是表意的而已。
icyomik 在 2010年09月18日 14:49 说:【 】
原来还有这样的事。
楼上和作者都不错,学习了。
wood 在 2010年09月30日 23:15 说:【 】
for k, val in d: pass
这算是还有一种方法吧
observer 在 2010年12月12日 23:27 说:【 】
我一直这么用的: for k,v in d.items()
第一次知道原来可以for k in d,瀑布汗
Code之行人 在 2011年01月13日 11:03 说:【 】
看到上面的讨论,我受教了