一段关于Python字典遍历的“争论”


Posted on 2010年10月31日 6,249 阅读



小弟我今天吃饱了饭逛大神们的blog,发现bones的某篇日志下面这么一段小小的争论。

先摘抄下:



1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27



#这里初始化一个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的异常
bones的解决方法是遍历字典键值,以字典键值为依据遍历,这样改变了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 = b del b print a




1



2



3



4



5



6



7



8



9



10



#!/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 = b



del b



print a



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


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




1



2



3



val = float ( raw_input ( "Age: " ) )



status = ( "working" , "retired" ) [ val > 65 ]



print "You should be" , status



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


#V1 if X else V2 s = None a = "not null" if s == None else s print a #'not null'




1



2



3



4



5



#V1 if X else V2



s = None



a = "not null" if s == None else s



print a



#'not null'




后来发帖在

华蟒用户组(中文Python技术邮件列表)

http://groups.google.com/group/python-cn/browse_thread/thread/047b1401e96c4b88#)中提到后众多大神解答如下:

>>> 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 }

也就是赖勇浩原文里面提到的语法