在学校搞科研的时候用Python做数据分析,所以对其用法也没有深入了解,实习的时候用Python搞的开发,当时觉得没什么。但最近看了《Python Cookbook》发现自己当初写的代码太垃圾了,其中原因之一就是对字典不够熟悉、了解的过于片面。
现在我就结合《Python Cookbook》中所讲,结合自己的认识讲一讲Python中字典这一数据结构的用法。
字典是另一种可变容器模型,且可存储任意类型对象。字典是通过名字(key)来引用值(value)的数据结构,并且把这种数据结构称为映射。直接举个简单的例子:
# 通用表达式 dic = {key1:value1, key2:value2, key3:value3} # 举例 stu = {"ZhangSan":19, "LiSi":20, "WangWu":20}
以上的dic
和stu
都是字典,可以发现字典是由大括号括起来的,由多个键和其对应的值构成的键值对(key-value)组成,键(key)和值(value)中间以冒号隔开。
字典的基础操作包括:创建、增删改查,以及一些内置方法;这里不详细说,因为一是比较简单,二是如果展开说会导致本文篇幅太长。如果基础操作不懂,大家可以参考菜鸟教程,文章1,文章2。我想重点讲一讲出了基础操作之外的操作,这也是开发中常遇到的情况。
假设现在有一个需求:需要创建一个字典,字典中的键值关系是一对多,那么一般有两种方法实现,一是自己创建一个空字典,然后一个个插入元素,二是利用现有的数据结构defaultdict
。
自己创建有两种方法:如果数据量比较少,并且都已知,可以直接创建;如果数据量比较多,可以先创建一个空字典,然后一步步插入数据。下面分开讲。
对于字典,如果我们想要一个键映射多个值,那么就需要将这多个值放到另外的容器中,比如列表或者集合里面。可以像下面这样构造字典:
dic1 = { 'a' : [1, 2, 3], 'b' : [4, 5] } dic2 = { 'a' : {1, 2, 3}, 'b' : {4, 5} }
选择使用列表还是集合取决于实际需求:如果想保持元素的插入顺序就应该使用列表;如果想去掉重复元素就使用集合(并且不关心元素的顺序问题);一般不用元组作为value,因为元组是不可变的。
如果我们自己创建一个一个多值映射字典会比较麻烦,如下代码所示,pairs
中有很多映射关系,自己实现起来就比较麻烦。
pairs = {('a',1),('a',2),('a',3),('b',4),('b',5)} d = {} for key, value in pairs: if key not in d: d[key] = [] d[key].append(value) print(d)
输出如下:
Python中 collections 模块中的 自带defaultdict ,我们可以使用它来构造3.1中的字典。defaultdict 的一个特征是它会自动初始化每个 key 刚开始对应的值,所以我们只需要关注添加元素操作就可以。对于3.2中的操作,我们可以改为如下:
from collections import defaultdict pairs = {('a',1),('a',2),('a',3),('b',4),('b',5)} dic = defaultdict(list) for key,val in pairs: dic[key].append(val)
假如我们创建一个了字典,在输出(迭代)时想按元素被插入的顺序进行输出,可 以 使 用 collections 模 块 中 的OrderedDict
类,示例代码如下;
from collections import OrderedDict d = OrderedDict() d['foo'] = 1 d['bar'] = 2 d['spam'] = 3 d['grok'] = 4 for key in d: print(key, d[key]) print("字典为:", d)
输出为:
可以看出输出的顺序和元素插入的顺序是一样的,也可以理解为“先进先出”
有时候我们需要在数据字典中执行一些计算操作(比如求最小值、最大值、排序等等),那么该如何操作呢?
假设有如下字典,key表示股票名,value代表价格:
prices = { 'ACME': 45.23, 'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.20, 'FB': 10.75 }
如果直接执行操作max(prices)
和min(prices)
得到的结果是键的“最大”和“最小”,而不是按照值进行排序。必须执行max(prices.values())
和min(prices.values())
,这样就能得到股票的最高和最低价格,但是无法得知对应的股票名称。
max()
和min()
函数中的key具体代码如下
max(prices.items(),key=lambda x:x[1]) # 输出为:('AAPL', 612.78) min(prices.items(),key=lambda x:x[1]) # 输出为:('FB', 10.75)
key参数需要传递一个函数名,在进行比较前首先用这个函数对前面用来比较的每个元素进行一次预处理,也就是求max或者min的规则。后面对key传入的是一个lambda函数,表示是按照元组中第1个元素进行求最值的,所以返回是的最大value对应的元组。
在上述代码中prices.items()
是dict_items([('ACME', 45.23), ('AAPL', 612.78), ('IBM', 205.55), ('HPQ', 37.2), ('FB', 10.75)])
,是把prices字典中的键值对转换成元组并存放在数组中。
如果还看不懂,请参考文章。
zip()
函数可以执行以下代码。其中zip()
函数是把对象中对应的元素打包成一个元组,返回这些元组组成的列表。
min_price = min(zip(prices.values(), prices.keys())) # 输出为 (10.75, 'FB') max_price = max(zip(prices.values(), prices.keys())) # 输出为 (612.78, 'AAPL')
注意:
zip()
函数创建的是一个只能访问一次的迭代器 ,下面代码将会报错prices_and_names = zip(prices.values(), prices.keys()) print(min(prices_and_names)) # OK print(max(prices_and_names)) # ValueError: max() arg is an empty sequence
prices = { 'AAA' : 45.23, 'ZZZ': 45.23 } min(zip(prices.values(), prices.keys())) # 输出为(45.23, 'AAA') max(zip(prices.values(), prices.keys())) # 输出为 (45.23, 'ZZZ')
仍然可以利用在 min() 和 max() 函数中提供 key 函数参数来实现,即获取最小值或最大值对应的键的信息,具体代码如下:
min(prices, key=lambda k: prices[k]) # Returns 'FB' max(prices, key=lambda k: prices[k]) # Returns 'AAPL
sorted()
函数中的参数keysorted(prices.items(), key=lambda x:x[1])
输出如下:
zip()
函数prices_sorted = sorted(zip(prices.values(), prices.keys())) print(prices_sorted)
输出如下:
假设有如下两个字典
a = { 'x' : 1, 'y' : 2, 'z' : 3 } b = { 'w' : 10, 'x' : 11, 'y' : 2 }
有如下操作可以寻找两个字典的异同点:
# 寻找a和b中相同的key a.keys() & b.keys() # { 'x', 'y' } # 寻找a和b中键值对相同的元组 a.items() & b.items() # { ('y', 2) } # 寻找在a中不在b中的key,即a中有,b中没有 a.keys() - b.keys() # { 'z' }
利用以上操作也可以实现字典的过滤操作,比如以现有字典构造一个排除几个指定键的新字典,如下所示,表示过滤掉a在集合中的key。
c = {key:a[key] for key in a.keys() - {'z', 'w'}} # c is {'y': 2, 'x': 1}