I am LAZY bones? AN ancient AND boring SITE

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 ,这个看来只能通过其他方式来保证了。

最后修改时间: 2010年09月17日 21:44

本文章发表于: 2010年09月17日 21:44 | 所属分类:编程相关. | 您可以在此订阅本文章的所有评论. | 您也可以发表评论, 或从您的网站trackback.

14 个评论 关于: “python编程细节──遍历dict的两种方法比较”

  1. tusooa 在 2010年09月17日 22:36 说:回复

    多线程,try一下呗。

  2. firefoxmmx 在 2010年09月17日 22:47 说:回复

    哦。。这个问题我也遇到过。。

  3. oceanboo 在 2010年09月18日 02:21 说:回复

    多线程如果不要效率的花,还是lock一下吧,这样最保险了。

  4. Iven 在 2010年09月18日 08:04 说:回复

    因为 for k in d: 是迭代,所以才会这样的,我记得貌似这样写会有警告吧……

  5. 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的特点,只不过变量名是表意的而已。

  6. icyomik 在 2010年09月18日 14:49 说:回复

    原来还有这样的事。
    楼上和作者都不错,学习了。

  7. wood 在 2010年09月30日 23:15 说:回复

    for k, val in d: pass

    这算是还有一种方法吧

  8. observer 在 2010年12月12日 23:27 说:回复

    我一直这么用的: for k,v in d.items()
    第一次知道原来可以for k in d,瀑布汗

  9. Code之行人 在 2011年01月13日 11:03 说:回复

    看到上面的讨论,我受教了

发表评论