前面五小节中,我们学习了使用defaultdict(list/set)来初始化可以一个键保存多个值的字典、使用OrderedDict来创建在读取时能够保持插入顺序的字典、使用zip处理键值对以方便对字典进行简单地计算(min、max、sorted)以及对dict.keys()和dict.items()进行集合运算(&/-)和利用set和generator在保持字典内容顺序的同时删除字典中特定部分重复的内容。
1.11 为切片命名
在对iteratable的结构进行切片操作时,我们常常会重复某些切片行为,在具体的场景中,我们可以对这些切片行为进行命名,示例如下:
1 2 3 4 5 6 7 8 9 10 11
| itema=[1,2,3,4,5] itemb='ilovepython' a=slice(2,4) b=slice(2,5,2) print(itema[a],'\n',itema[b],'\n',itemb[a],'\n',itemb[b]) del itema[a] print('\n',itema)
|
在对切片命名时,实际上生成了一个切片对象
1 2 3 4
| a=slice(2,6,2) print(a.start) print(a.stop) print(a.step)
|
为了防止索引值溢出的情况,可以使用indices方法对切片对象的切片效果进行修改
1 2 3 4 5 6 7
| a=slice(2,100,3) items='helloworld' print(a.indices(len(items))) for i in range(*a.indices(len(items))): print(i,items[i],',')
|
indices方法会根据传入的值来得到恰当的切片参数,进而可以适应不同情况的切片任务。
1.12 寻找序列中出现次数最多的元素
collections.Counter是一个被设计用来解决此类问题的类,将想要处理的list传入类中,将得到一个对象,这个对象可以调用most_common方法,用于返回出现次数最多的几个值。
1 2 3 4 5 6 7 8 9
| words=[ 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes', 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into', 'my', 'eyes', "you're", 'under' ] from collections import Counter word_counts = Counter(words) top_thress = word_counts.most_common(3)
|
使用word_counts[word]可以得到上一次统计结果中word的出现次数。
Counter对象还有update方法,该方法以一个list组为参数,可以更新其统计的数量信息。
鲜为人知的一点是,Counter对象可以进行集合运算,操作符为+,-,如:
1 2 3 4
| a=Counter(words) b=Counter(morewords) c=a+b d=a-b
|
Counter对象打印出来的内部结构为{item1:count1,item2,count2,...},按照从大到小的顺序排列,在任何需要统计数据的场景下,Counter都是一个出色的工具函数。
1.13 根据共同的key来对一系列dict值进行排序
操作符模块的itemgetter方法非常适合解决这样的问题,该方法可传入一个或者多个键名,返回键名对应的键值,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13
| rows = [ {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004} ] from operator import itemgetter rows_by_fname=sorted(rows,key=itemgetter("fname"))
rows_by_uid=sorted(rows,key=itemgetter("uid"))
rows_by_lfname=sorted(rows,key=itemgetter("lname","fname"))
|
在不使用itemgetter的情况下,可以用lambda来代替
1 2
| rows_by_fname=sorted(rows,key=lambda r:r['fname']) rows_by_llfname=sorted(rows,key=lambda r:(r['lname'],r['fname']))
|
itemgetter同样可以用于min和max方法中:
1 2
| rows_min=min(rows,key=itemgetter('uid')) rows_max=max(rows,key=itemgetter('uid'))
|
1.14 根据某种性质来比较原生不支持比较操作的对象
比如当你有一系列的实例化的类,你想要根据每一个实例的某个性质来对这些实例进行排序,但这些实例原生是不支持排序方法的,因此你需要在sorted函数中传入处理函数以得到排序标准,这里同样可以使用lambda来解决这类问题,但是,无论是itemgetter还是本节所要讲的attrgetter,他们都要比lambda的执行效率高一些,因此,在注重性能的情况下,优先使用operator库中的函数。
1 2 3 4 5 6 7 8
| class User: def __init__(self,user_id): self.user_id=user_id def __repr__(self): return 'User({})'.format(self.user_id) users=[User(1),User(4),User(3),User(2)] sorted_users1=sorted(users,key=lambda u:u.user_id) sorted_users2=sorted(users,key=attrgetter('user_id'))
|
上面代码中两种写法得到的结果是一样的,但是使用attrgetter会更快一些。同itemgetter一样,attrgetter也可以用于min、max方法中并同样可以传入多个参数作为索引。
1.15对字典序列根据特定键值进行分组
当你得到了由一系列结构相似的dict组成的序列,你想要将这些数据按照某种规则进行分组,此时可以使用itertools.groupby()方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| rows = [ {'address': '5412 N CLARK', 'date': '07/01/2012'}, {'address': '5148 N CLARK', 'date': '07/04/2012'}, {'address': '5800 E 58TH', 'date': '07/02/2012'}, {'address': '2122 N CLARK', 'date': '07/03/2012'}, {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}, {'address': '1060 W ADDISON', 'date': '07/02/2012'}, {'address': '4801 N BROADWAY', 'date': '07/01/2012'}, {'address': '1039 W GRANVILLE', 'date': '07/04/2012'}, ] from operator import itemgetter from itertools import groupby rows.sorted(key=itemgetter('date')) grouped_rows=groupby(rows,key=itemgetter('date'))
|
上述代码得到的结果,是以日期为键名,以相应分组结果组成的list为键值的序列。由于groupby只能会对相邻的数据进行同类检验,因此必须先对source进行排序,不然可能得不到你想要的结果。你可能会忍不住跳起来说,何必这么dirty,只要使用defaultdict再来个循环就搞定了:
1 2 3 4
| from collections import defaultdict rows_by_date=default(list) for row in rows: rows_by_date[row['date']].append(row)
|
这种方式当然是可以的,而且省去了排序的步骤,但是由于defaultdict在内存方面消耗会较大,因此不建议用来处理大批量的数据,当然,在无需关心内存的情况下,上述代码要比排序后再使用groupby要来的快一些。