零基础入门学习Python(第2版)
上QQ阅读APP看书,第一时间看更新

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