2.7 字典
2.7.1 字典概述及声明
本节我们将介绍一种新的数据结构——字典,属于核心数据类型——映射。字典可以说是Python中除了列表使用频率最高的一个数据类型。
那究竟什么是字典呢?对于字典必须先明确两个概念:键(key)和对应值(value)。因为有些时候,有些数据的结构往往是成对出现的,每一个键对应一个值,我们要保存它们之间的关系,要把很多这样的项目(一对,即一个键和一个对应值)添加到一个大的数据结构中,在这种情况下就非常适合使用字典。例如,我们想建立一个电话号码本,人名就可以当作一个键(key),每一个电话号码就可以是这个键(人名)的对应值(value)(见表2-12)。
表2-12 键与对应值示例
以上这种数据结构就叫作字典(dictionary)。
1.字典的特性
1)通过键(key)而非下标索引(位置偏移)访问数据
相对于列表来讲,字典的数据访问不是通过下标索引,而是通过它的键来完成的。这里的键叫作“key”,它所存储的值叫作“value”,所以它的存储结构基本上就是一个键一个值,即平时我们所说的键值对。
2)可包含任意对象的无序集合
当在创建列表时,它里边的元素的顺序跟创建时候的顺序是保持一致的,并且可以通过特定的方法或全局函数来将列表中的元素进行排序。但在默认的字典中,它的元素是无序的,通过后面的代码将会看到,创建字典时的顺序和呈现时的顺序可能会不一样[4]。
说明:显示的顺序可能和声明时的顺序并不保持一致。
3)可变长度、异质、可以任意嵌套
这一点和列表非常类似,只是它的存储结构有差异而已。
4)属于可变映射
序列有可变序列和不可变序列之分。在映射中,字典属于可变映射。
5)对象引用表(Hash Table)
这个特点与它的存储本质有关,它是一个对象引用的表,它的存储机制就是平时所说的哈希表。
2.创建字典的方法
Python 中的字典用大括号表示,每个项目的键和对应值之间用冒号分隔,冒号左边是键,右边是对应值;每个项目之间用“,”分隔。因为 Python 是一门非常灵活的语言,所以,在同一个字典中,可以包含多种不同类型的数据。
1)创建一个空字典{}
2)创建包含若干元素的字典{key:value}
在介绍创建包含多个数据的字典之前,先了解一下字典跟元组和列表之间的区别。
使用列表可以存储一系列的信息,假定想声明一个学生信息,当然,这个学生信息可能会包含很多内容,可能要关注他的姓名、学号、年龄及他的籍贯等。这时,可以把这几个值放在一个列表里,因为列表可以包含任意的对象。
在创建列表的时候,开发人员明确知道它的第一、第二、第三个元素对应的是什么信息,但是计算机并不了解,或者说你的开发伙伴并不明白你的意图是什么。也就是说,如果想进一步描述"Tom"是指姓名、19 是指年龄,等等,则需要做更多的工作。因此,要存储这一类有特定标签的值,列表并不是最好的选择,此时就可以使用字典来表示。
用花括号来表示字典,将我们所关注的信息的标签和实际的值用键值对的方式来进行存储。比如关注姓名,那就输入字符串"name",之后输入一个冒号,再输入"Tom",这便是第一个键值对,然后逗号分隔,继续输入第二个键值对等,这样就声明了一个字典结构的学生信息。其中"name"是一个键,"Tom"是一个值。
可能你会发现这个顺序和声明的时候不一致了,当然也可能是一致的。这对应字典“可包含任意对象的无序集合”的特性,即它的顺序是不确定的。事实上,在 Python中,如果确实需要一个能够保存正确顺序的字典也是可以的,但它属于另外一个数据类型,并且在一个专门的包中,它是扩展的字典,而这里介绍的是标准的字典。
注意:在实际开发过程中,字典中的键不一定是字符串,也可以是浮点型数值、元组,但是基本要求是它的键只能是不可变的元素,可变的元素如列表是不能作为键的,这一点一定要注意。
虽然从语法上来说,整数、小数、元组,只要是不可变的元素都是可以作为键,但在实际开发的时候,键是有实际意义的,代表具体的要存储的信息。
3.类型转换——dict()函数
在介绍整型、浮点型、列表、元祖时,都有一系列的全局函数,如 int()、float()、list()、tuple()等将某一个特定的对象转换为目标类型。在字典中也有一个 dict()函数,它能够将参数中指定的信息转换为字典的数据结构。其语法如下:
注意:括号里参数的形式和一般函数参数传入的方式不同,是以关键字参数形式进行传参的。键值对是以参数的形式传入的,键等于什么值,即将函数参数指定的对象转化为对应的目标类型。这里要传入的是键值对的信息,键 key 和对应值 value 以等号作为分隔符形成一个键值对,一个键值对作为一个参数,多个参数即多个键值对之间仍然用逗号进行分隔。
以上结果表明这种声明方式与用花括号方式声明的结果并没有什么区别。
但请注意:在用 dict()函数声明字典的方式中,以参数等于值(key=value)的形式呈现,参数在输入对不要加引号(字符串),不加引号声明的键默认会加上一个引号。即它以字符串的类型来转换输入的键。但这种声明方式的键不能是整型、浮点数型、元组及列表。
实际上,使用 dict()函数的形式来声明字典,还有更多更灵活的方式,比如将一个列表转换为一个字典。注意:列表的每个元素又是一个元组或一个列表。
dict([(key,value),(key,value)]):将由元组构成的列表转换为字典。
在这种构造字典的方法中,列表的元素也可以是一个列表,甚至一些元素是列表,另一些元素是元组也是可以的,但这没有太大的实际意义,只是单纯地从语法角度来说是可行的。实际使用时我们尽量避免这种情况,因为一个知识点的学习和掌握是用来解决实际问题的,如果实际场景中并没有这种现象,我们就不要单纯地从语法角度去理解。
假设现在有一个列表包含了一些信息,现在希望把列表所存储的信息作为字典的键,如已有列表keys=['name','age','job'],希望把列表keys的信息转换成字典的3个键,能否进行这样的转换?我们知道字典总是以键值对的形式来存储的,现在没有值只有键可以吗?
4.字典的方法fromkeys()
通过字典里的一个方法 fromkeys()来声明,将列表中的每个元素作为字典里的一个键,没有值的地方设定为“None”,就是空对象。当然它的值是可以修改的,后面会介绍是如何修改的。
利用 dict()函数进行转换时,有两种方式,一种是键值对以“key=value”的形式传入,另一种是传入的参数是一个序列,或者可迭代对象,它们是有规律的键值对。另外,使用 dict()自带的方法 fromkeys()也能进行转换(注意:不是函数,而是一个方法转换),此时是将一个序列的值作为字典的键来进行转换的,键所对应的值设定为“None”。
在实际开发过程中,声明字典的两种常用方法是使用花括号声明键值对、冒号隔开({key:value}),以及使用函数进行转换,键值对中间以等号隔开(dict(key=value))。
5.元素去重
一个字典中不允许同一个键出现两次,即每个键都是独一无二的!因此,将一个列表或元组转换成字典的过程实际上达到了将相同的多余元素删除的目的。
2.7.2 字典元素的访问
字典是一个无序的结构,对字典的访问操作是通过键而不是下标索引来进行的。同样,对字典元素的修改、增加,或者删除字典元素也都是通过键而非下标索引来进行的。假设定义了字典dt,对字典元素的访问操作如表2-13所示。
表2-13 字典元素的访问操作
2.7.3 字典常用方法
字典在Python内部已经采用面向对象方式实现了,灵活使用字典的方法可以提高编写代码的效率。字典的常用方法如表2-14所示。
表2-14 字典的常用方法
关于字典常用方法的几点说明:
1)get()方法的使用
通过 get()方法访问字典元素的方式与中括号的访问方式的区别:即使访问的键不存在,它也不会抛出异常,而是返回一个“None”。如果返回“None”不能满足要求,还可以返回提示信息。如:
这里,后一项“未找到!”为方法 get()未能成功获取时返回的一个默认值(字符串),这样可能更能满足实际开发时的需求。
2)获取所有键:字典名.keys()
当访问元素的时候,不管是用中括号还是 get()方法,它总是根据给定的一个键得到相对应的一个值,而在有些情况下,希望得到字典的所有的键,或者是所有的值,又或者是所有的键值对,此时可以通过方法 keys()来得到所有的键。其返回的类型是一个视图,跟列表很像,并可以将获得的视图转换成列表。
类型“dict_keys”本质是一个视图,但是在实际开发的过程中,如果希望像列表一样对它进行操作,那就把它转换成列表。如果只是希望循环打印它的结果,则不用转换列表而直接用fon-in遍历循环就能达成目标。
3)获取所有键值对:字典名.items()
如果希望一次获取字典所有的键值对信息,利用方法 items()可以实现这个目的。注意,方法items()得到的仍然是一个视图,但视图里的键值对是用元组呈现的。
方法 items()得到的每一个键值对数据放在一个元组里,元组的第一个元素是键,第二个元素是值。因此,可以通过 for-in 遍历循环打印输出每一个键值对信息(“.format()”格式中的花括号是占位符)。
4)复制字典:字典名.copy()
一般不要用赋值的方式来将一个字典赋值给另一个字典,因为这时它们指向的是同一个对象,对一个字典进行操作会影响另一个字典的元素。如果的确新建的字典的信息来自一个已经创建好了的字典,此时可以通过方法copy()来完成。
5)更新字典:字典名1.update(字典名2)
方法 update()是用字典 2 中的元素来更新字典 1 中的相同元素,不同的元素添加到字典 1 里,所以,该方法实际上是合并更新(当两个字典里没有相同的元素时就相当于合并)。看下面的例子。
强调:字典中的信息都是按键值对的形式存储的,存储的值都是普通的字段或一个嵌套的字典。而在实际开发工作中,字典的值不仅仅局限于某一个常规类型的字典值,它也可以是一个行为方法——函数。函数是定义一个行为的,其实可以将一个函数作为值赋给一个字典。
比如有一个自定义函数say_hello()。在声明一个字典的时候,希望把“say_hello()”这个行为作为字典中一个键的值,那么直接在值的地方输入“say_hello”,但请注意,此时在“say_hello”后千万不要加小括号,因为加上小括号表示在此处执行函数say_hello(),并且将执行函数返回的值放在这个地方这不是我们希望的。仔细理解下面的代码:
注意:在访问字典的元素时,如果想执行“say_hello”这个行为方法,正确的表示方式是person['hello'](),由于person['hello']获得键'hello'的值,而键'hello'所对应的值是一个函数的函数名 say_hello,因此,这里的 person['hello']就相当于“say_hello”这个函数的名称,要想调用该函数,就必须加上括号。
这一点非常灵活,今后在实际项目开发中可用它实现很多有效或有趣的功能。
Python 的数据结构是比较有特色的,活用字典、列表两种数据结构会有出人意料的效果。
字典可以理解成封装好的哈希表,可以非常方便地处理树形结构。