第7章 字典和集合
7.1 字典:当索引不好用时
视频讲解
图7-1 映射
有一天你想翻开《新华字典》,查找一下“龟”是不是一种鸟。如果是按拼音检索,你总不可能从字母a开始查找吧?而应该直接翻到字母g在字典中的位置,然后接着找到gui的发音,继而找到“龟”字的释义:广义上指龟鳖目的统称,狭义上指龟科下的物种。
在Python中也有字典,就拿刚才的例子来说,Python的字典把这个字(或单词)称为“键(key)”,把其对应的含义称为“值(value)”。另外值得一提的是,Python的字典在有些地方称为哈希(hash),有些地方称为关系数组,其实这些都与今天要讲的Python字典是同一个概念。
字典是Python中唯一的映射类型,映射是数学上的一个术语,指两个元素集之间元素相互“对应”的关系,如图7-1所示。
映射类型区别于序列类型,序列类型以数组的形式存储,通过索引的方式来获取相应位置的值,一般索引值与对应位置存储的数据是毫无关系的。
举个例子:
>>> brand = ["李宁", "耐克", "阿迪达斯", "鱼C工作室"] >>> slogan = ["一切皆有可能", "Just do it", "Impossible is nothing", "让 编程改变世界"] >>> print("鱼C工作室的口号是:%s" % slogan[brand.index("鱼C工作室")]) 鱼C工作室的口号是:让编程改变世界
列表brand、slogan的索引和相对的值是没有任何关系的,可以看出,在两个列表间,索引号相同的元素是有关系的(品牌对应口号),所以这里通过“brand.index('鱼C工作室')”这样的语句,间接地实现通过品牌查找对应的口号的功能。
这确实是一种可实现方法,但用起来多少有些别扭,而且效率还不高。况且Python是以简洁为主,这样的实现肯定是差强人意的。
7.1.1 创建和访问字典
先演示一下用法:
字典的使用非常简单,它有自己的标志性符号,就是用大括号({})定义。字典由“键”和“值”共同构成,每一对键值组合称为“项”。
在刚才的例子中,李宁、耐克、阿迪达斯、鱼C工作室这些品牌就是键,而一切皆有可能、Just do it、Impossible is nothing、让编程改变世界,这些口号就是对应的值。
注意:
字典的键必须独一无二,但值则不必。值可以取任何数据类型,但必须是不可变的,如字符串、数或元组。
要声明一个空字典,直接用大括号即可:
>>> empty = {} >>> empty {} >>> type(empty) <class 'dict'>
也可以使用dict()内置函数来创建字典:
>>> dict1 = dict((('F', 70), ('i', 105), ('s', 115), ('h', 104), ('C', 67))) >>> dict1 {'F': 70, 'i': 105, 's': 115, 'h': 104, 'C': 67}
有读者可能会问,为什么上面的例子中出现这么多小括号?
因为dict()函数的参数可以是一个序列(但不能是多个),所以要打包成一个元组(或列表)序列。
当然,如果嫌上面的做法太麻烦,还可以通过提供具有映射关系的参数来创建字典:
>>> dict1 = dict(F=70, i=105, s=115, h=104, C=67) >>> dict1 {'F': 70, 'i': 105, 's': 115, 'h': 104, 'C': 67}
这里要注意的是,键的位置不能加上表示字符串的引号,否则会报错:
>>> dict1 = dict('F'=70, 'i'=105, 's'=115, 'h'=104, 'C'=67) SyntaxError: keyword can't be an expression
访问字典里的值与访问序列类似,只需要把相应的键放入方括号即可,如果该键不在映射中,则抛出KeyError:
还有一种创建方法是直接给字典的键赋值,如果键已存在,则改写键对应的值;如果键不存在,则创建一个新的键并赋值:
>>> dict1 {'F': 70, 'i': 105, 's': 115, 'h': 104, 'C': 67} >>> dict1['x'] = 88 >>> dict1 {'F': 70, 'i': 105, 's': 115, 'h': 104, 'C': 67, 'x': 88} >>> dict1['x'] = 120 >>> dict1 {'F': 70, 'i': 105, 's': 115, 'h': 104, 'C': 67, 'x': 120}
注意:
字典不允许同一个键出现两次,如果同一个键被赋值两次,后一个值会被记住:
>>> courses = {"小甲鱼":"《零基础入门学习Python》", "不二如是":"《零基础入门学 习Scratch》", "小甲鱼":"《极客Python之效率革命》"} >>> courses {'小甲鱼': '《极客Python之效率革命》','不二如是': '《零基础入门学习Scratch》'}
键必须不可变,所以可以用数值、字符串或元组充当,如果使用列表那就不行了:
正所谓殊途同归,下面列举的五种方法都是创建同样的字典,大家仔细体会一下:
>>> a = dict(one=1, two=2, three=3) >>> b = {'one': 1, 'two': 2, 'three': 3} >>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3])) >>> d = dict([('two', 2), ('one', 1), ('three', 3)]) >>> e = dict({'three': 3, 'one': 1, 'two': 2}) >>> a == b == c == d == e True
有别于序列,字典是不支持拼接和重复操作的:
7.1.2 各种内置方法
视频讲解
1)fromkeys(seq[, value])
fromkeys()方法用于创建并返回一个新的字典,它有两个参数;第一个参数是字典的键;第二个参数是可选的,是传入键对应的值,如果不提供,那么默认是None。
举个例子:
>>> dict1 = {} >>> dict1.fromkeys((1, 2, 3)) {1: None, 2: None, 3: None} >>> dict2 = {} >>> dict2.fromkeys((1, 2, 3), "Number") {1: 'Number', 2: 'Number', 3: 'Number'} >>> dict3 = {} >>> dict3.fromkeys((1, 2, 3), ("one", "two", "three")) {1: ('one', 'two', 'three'), 2: ('one', 'two', 'three'), 3: ('one', 'two', 'three')}
上面最后一个例子告诉我们做事不能总是想当然,有时候现实会给你狠狠的一棒。fromkeys()方法并不会将值"one"、"two"和"three"分别赋值键1、2和3,因为fromkeys()把("one", "two", "three")当成一个值了。
2)keys(),values()和items()
访问字典的方法有keys()、values()和items()。keys()用于返回字典中的键,values()用于返回字典中所有的值,那么,items()当然就是返回字典中所有的键值对(也就是项)。
举个例子:
>>> dict1 = {} >>> dict1 = dict1.fromkeys(range(32), "赞") >>> dict1.keys() dict_keys([0, 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, 28, 29, 30, 31]) >>> dict1.values() dict_values(['赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞', '赞']) >>> dict1.items() dict_items([(0, '赞'), (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, '赞'), (28, '赞'), (29, '赞'), (30, '赞'), (31, '赞')])
字典可以很大,有时候我们并不知道提供的项是否在字典中存在,如果不存在,Python就会报错:
对于代码调试阶段,报错可以让程序员及时发现程序存在的问题并修改。但是如果程序已经发布了,那么经常报错的程序肯定是会被用户遗弃的。
3)get(key[, default])
get()方法提供了更宽松的方式去访问字典项,当键不存在的时候,get()方法并不会报错,只是默默地返回了一个None,表示啥都没找到:
>>> dict1.get(31) '赞' >>> dict1.get(32) >>>
如果希望找不到数据时返回指定的值,那么可以在第二个参数设置对应的默认返回值:
>>> dict1.get(32, "木有") '木有'
如果不知道一个键是否在字典中,那么可以使用成员资格操作符(in或not in)来判断:
>>> 31 in dict1 True >>> 32 in dict2 False
在字典中检查键的成员资格比序列更高效,当数据规模相当大的时候,两者的差距会很明显(注:因为字典是采用哈希方法一对一找到成员,而序列则是采取迭代的方式逐个比对)。最后要注意的一点是,这里查找的是键而不是值,但是在序列中查找的是元素的值而不是元素的索引。
如果需要清空一个字典,则使用clear()方法:
>>> dict1 {0: '赞', 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: '赞', 28: '赞', 29: '赞', 30: '赞', 31: '赞'} >>> dict1.clear() >>> dict1 {}
有的读者可能会使用变量名赋值为一个空字典的方法来清空字典,这样的做法其实存在一定的弊端。
下面给大家解释这两种清除方法有什么不同。
>>> a = {"姓名":"小甲鱼", "密码":"123456"} >>> b = a >>> b {'姓名': '小甲鱼', '密码': '123456'} >>> a = {} >>> a {} >>> b {'姓名': '小甲鱼', '密码': '123456'}
从上面的例子中可以看到,a、b指向同一个字典,然后试图通过将a重新指向一个空字典来达到清空的效果时,我们发现原来的字典并没有被真正清空,只是a指向了一个新的空字典而已。所以,这种做法在一定条件下会留下安全隐患(例如,账户的数据和密码等资料有可能会被窃取)。
推荐的做法是使用clear()方法:
>>> a = {"姓名":"小甲鱼", "密码":"123456"} >>> b = a >>> b {'姓名': '小甲鱼', '密码': '123456'} >>> a.clear() >>> a {} >>> b {}
4)copy()
copy()方法是用于拷贝(浅拷贝)整个字典:
>>> a = {1:"one", 2:"two", 3:"three"} >>> b = a.copy() >>> id(a) 63239624 >>> id(b) 63239688 >>> a[1] = "four" >>> a {1: 'four', 2: 'two', 3: 'three'} >>> b {1: 'one', 2: 'two', 3: 'three'}
5)pop(key[, default])和popitem()
pop()是给定键弹出对应的值,而popitem()是弹出一个项,这两个比较容易理解:
>>> a = {1:"one", 2:"two", 3:"three", 4:"four"} >>> a.pop(2) 'two' >>> a {1: 'one', 3: 'three', 4: 'four'} >>> a.popitem() (1, 'one') >>> a {3: 'three', 4: 'four'}
6)setdefault(key[, default])
setdefault()方法和get()方法有点相似,但是,setdefault()在字典中找不到相应的键时会自动添加:
>>> a = {1:"one", 2:"two", 3:"three", 4:"four"} >>> a.setdefault(3) 'three' >>> a.setdefault(5) >>> a {1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: None}
7)update([other])
最后一个是update()方法,可以利用它来更新字典:
>>> pets = {"米奇":"老鼠", "汤姆":"猫", "小白":"猪"} >>> pets.update(小白="狗") >>> pets {'米奇': '老鼠', '汤姆': '猫', '小白': '狗'}
还记得在6.2节的末尾我们埋下了一个伏笔,在讲到收集参数的时候,我们说Python还有另一种收集方式,就是用两个星号(**)表示。两个星号的收集参数表示为将参数们打包成字典的形式,现在讲到了字典,就顺理成章地给大家讲讲吧。
收集参数其实有两种打包形式:一种是以元组的形式打包;另一种则是以字典的形式打包。
当参数带两个星号(**)时,传递给函数的任意数量的key=value实参会被打包进一个字典中。那么有打包就有解包,再来看下一个例子:
>>> a = {"one":1, "two":2, "three":3} >>> test(**a) 有 3 个参数 它们分别是: {'three': 3, 'one': 1, 'two': 2}