字典是Python中唯一内建的映射类型,字典指定值并没有特殊顺序,都存储在一个特殊的键(Key)里,键可以是数字、字符串或元组。
字典是另一种可变容器模型,可存储任意类型对象。
字典的使用
字典这个数据结构的功能就跟它的名字一样,可以像《汉语字典》一样使用。在使用汉语字典时,我们可以从头到尾一页一页查找某个字,也可以通过拼音索引或笔画索引快速找到某个字,在汉语字典中找拼音索引和笔画索引非常轻松简单。
在Python中对字典进行了构造,让我们可以轻松查到某个特定的键(类似拼音或笔画索引),从而通过键找到对应的值(类似具体某个字)。
假如需要创建一个可以存储学生姓名和学号的小型数据库,应该怎么做呢?我们尝试使用之前学习过的列表实现,例如:
>>> students=['小萌','小智','小强','小张','小李']
>>> numbers=['1001','1002','1003','1004','1005']
>>> print('小智的学号是:',numbers[students.index('小智')])
小智的学号是: 1002
输出结果是我们想要的,但是当数据量比较大时,显然不适用。数据量比较大时,意味着要对学生姓名列表创建一个有同样多元素的学号列表,一旦列表发生变更,就要将学号列表和学生姓名列表进行逐步比对,以进行相应变更。
我们想要的真正效果是像使用index方法一样,index返回的是索引位置,我们希望直接返回索引位置上的值,比如:
>>> print('小智的学号是:',numbers['小智'])
小智的学号是: 1002
这种方式可以实现,如果numbers是字典,就可以这么操作并得到结果。
创建和使用字典
字典的创建格式如下:
>>> d = {key1 : value1, key2 : value2 }
字典由多个键及其对应的值构成的对组成(把键/值对称为项)。字典的每个键/值(key/value)对用冒号(:)分割,每个项之间用逗号(,)分割,整个字典包括在花括号({})中。空字典(不包括任何项)由两个大括号组成,如{}。
键必须是唯一的,但值不必。值可以取任何数据类型,键必须是不可变的,如字符串、数字或元组。
下面是一个简单的字典示例:
>>> dict = {'小萌': '1001', '小智': '1002', '小强': '1003'}
也可以为如下形式:
>>> dict1 = { 'abc': 456 }
>>> dict2 = { 'abc': 123, 98.6: 37 }
1 dict函数
可以用dict函数,通过其他映射(如其他字典)或键/值序列对建立字典,例如:
>>> student=[('name','小萌'),('number','1001')]
>>> detail=dict(student)
>>> print('学生详细信息:',detail)
学生详细信息: {'name': '小萌', 'number': '1001'}
>>> print('学生姓名:',detail['name'])
学生姓名: 小萌
>>> print('学生学号:',detail['number'])
学生学号: 1001
由输出结果看到,dict函数可以将序列转换为字典。字典的操作很简单,在5.1节期望的功能已经实现了。
dict函数可以通过关键字参数创建字典,例如:
>>> detail=dict(name='小智',number='1002')
>>> print('学生详细信息:',detail)
学生详细信息: {'name': '小智', 'number': '1002'}
由输出结果看到,通过关键字参数创建了字典。
通过关键字创建字典功能是dict函数非常有用的一个功能,可以多加运用。
2 字典的基本操作
字典的基本操作在很多方面与序列(sequence)类似,支持修改、删除等操作。下面进行具体的讲解。
1. 修改字典
向字典添加新内容的方法是增加新键/值对,修改或删除已有键/值对,例如:
>>> student={'小萌':'1001','小智':'1002','小强':'1003'}
>>> student['小强']='1005' #更新小强的学号
>>> print('小强的学号是:%(小强)s' % student)
小强的学号是:1005
>>> student['小张']='1006' #添加一个学生
>>> print('小张的学号是:%(小张)s' % student)
小张的学号是:1006
由输出结果看到,修改和添加均操作成功。
2. 删除字典元素
此处的删除指的是显式删除,显式删除一个字典用del命令,例如:
>>> student={'小强': '1005', '小萌': '1001', '小智': '1002', '小张': '1006'}
>>> print('删除前:',student)
删除前: {'小强': '1005', '小萌': '1001', '小智': '1002', '小张': '1006'}
>>> del student['小张'] #删除键“小张”
>>> print('删除后:',student)
删除后: {'小强': '1005', '小萌': '1001', '小智': '1002'}
由输出结果看到对应键/值被正确删除了。
除了删除键,还可以删除整个字典,例如:
>>> student={'小强': '1005', '小萌': '1001', '小智': '1002', '小张': '1006'}
>>> print('删除前:',student)
删除前: {'小强': '1005', '小萌': '1001', '小智': '1002', '小张': '1006'}
>>> del student #删除字典
>>> print('删除后:',student)
Traceback (most recent call last):
File "<pyshell#70>", line 1, in <module>
print('删除后:',student)
NameError: name 'student' is not defined
由输出结果看到,删除字典后就不能对字典进行访问了,因为执行del操作后字典就不存在了,因而会报变量没有定义的错误。
3. 字典键的特性
字典值可以没有限制地取任何Python对象,既可以是标准对象,也可以是用户定义的对象,但键不行。
需要记住以下两点:
(1)不允许同一个键出现两次。创建时如果同一个键被赋值两次,后面的值会被记住,例如:
>>> student={'小萌': '1001', '小智': '1002', '小萌': '1005'}
>>> print('学生信息:',student)
学生信息: {'小萌': '1005', '小智': '1002'}
由输出结果看到,一个键被赋值两次,后面的值会被记住。
(2)键必须不可变,可以用数字、字符串或元组充当,不能用列表,例如:
>>> field={['name']:'小萌','number':'1001'}
Traceback (most recent call last):
File "<pyshell#80>", line 1, in <module>
field={['name']:'小萌','number':'1001'}
TypeError: unhashable type: 'list'
由输出结果看到,不能用列表做键,会提示类型错误。
4. len函数
len(dict)函数用于计算字典元素的个数,即键的总数,例如:
>>> student={'小萌': '1001', '小智': '1002', '小强': '1005','小张': '1006'}
>>> print('字典元素个数为:%d 个' % len(student))
字典元素个数为:4 个
输出结果得到字典元素的个数为4。
5. type 函数
type(variable)函数返回输入的变量类型,如果输入变量是字典就返回字典类型,例如:
>>> student={'小萌': '1001', '小智': '1002', '小强': '1005','小张': '1006'}
>>> print('字典的类型为:',type(student))
字典的类型为: <class 'dict'>
输出结果为字典(dict)类型。
3 字典的格式化字符串
在第4章我们已经见过如何使用字符串格式化功能格式化元组中的所有值。如果使用字典(只以字符串作为键)而不是元组做这项工作,会使字符串格式化更有趣一些,例如:
>>> student={'小萌':'1001','小智':'1002','小强':'1003'}
>>> print('小强的学号是:%(小强)s' % student)
小强的学号是:1003
由操作结果看到,字典的格式化方式是在每个转换说明符中的%字符后加上用圆括号括起来的键,再跟上其他说明元素。
字典的格式化除了增加字符串键外,转换说明符还像以前一样工作。以这种方式使用字典时,只要所有给出的键都能在字典中找到,就可以获得任意数量的转换说明符。
4 字典和列表的区别
例如,以一个名字查找对应的学号。
如果用list实现,就要先在名字列表中找到对应的名字,再从学号列表取出对应的学号,list越长耗时越长。如果用dict实现,只需要一个名字和学号的对照表,就可以直接根据名字查找学号,无论这个表有多大,查找速度都不会变慢。
为什么dict查找速度这么快?
因为dict的实现原理和查字典一样。假设字典包含10000个汉字,我们要查某一个字,一种方法是把字典从第一页往后翻,直到找到我们想要的字为止,这种方法是在list中查找元素,list越大查找越慢。另一种方法是在字典的索引表里(如部首表)查这个字对应的页码,然后直接翻到该页找到这个字。无论找哪个字,这种查找速度都非常快,不会随着字典大小的增加而变慢。
dict就是第二种实现方法,给定一个名字,比如我们要查找5.2.3小节示例中“小萌”的学号,在dict内部就可以直接计算出“小萌”存放学号的“页码”,也就是1001存放的内存地址,直接取出来即可,所以速度非常快。
综上所述,list和dict各有以下几个特点:
dict的特点是:
(1)查找和插入的速度极快,不会随着key的增加而变慢。
(2)需要占用大量内存,内存浪费多。
list的特点是:
(1)查找和插入时间随着元素的增加而增加。
(2)占用空间小,浪费内存很少。
所以,dict是使用空间换取时间。
dict可以用在很多需要高速查找的地方,在Python代码中几乎无处不在,正确使用dict非常重要,需要牢记dict的键必须是不可变对象。
注意:dict内部存放的顺序和键放入的顺序没有关系。
字典方法
像其他内建类型一样,字典也有方法,这些方法非常有用,不过可能不像使用列表或字符串一样频繁使用。学习本节时可以先简单浏览了解一下有哪些方法可以使用,然后在需要时回过头来查看特定方法的具体用法。
1 clear()方法
clear()方法用于删除字典内的所有项。
clear()方法的语法如下:
dict.clear()
此语法中dict代表指定字典,该方法不需要参数。该函数是一个原地操作(类似于list.sort)函数,没有任何返回值(返回值为None)。
该方法使用示例如下:
>>> student={'小萌': '1001', '小智': '1002', '小强': '1005','小张': '1006'}
>>> print('字典元素个数为:%d 个' % len(student))
字典元素个数为:4 个
>>> student.clear()
>>> print('字典删除后元素个数为:%d 个' % len(student))
字典删除后元素个数为:0 个
由输出结果可知,字典调用clear方法后整个字典内所有项都被删除。
下面看两个示例。
示例1:
>>> x={}
>>> y=x
>>> x['key']='value'
>>> y
{'key': 'value'}
>>> x={}
>>> y
{'key': 'value'}
示例2:
>>> x={}
>>> y=x
>>> x['key']='value'
>>> y
{'key': 'value'}
>>> x.clear()
>>> y
{}
两个示例中,x和y最初对应同一个字典。示例1中,通过将x关联到一个新的空字典对它重新赋值,这对y没有任何影响,还关联到原先的字典。若想清空原始字典中所有元素,则必须使用clear方法,使用clear后,y的值也被清空了。
2 copy()方法
copy()方法返回一个具有相同键/值对的新字典。这个方法是浅复制(shallow copy),因为值本身是相同的,而不是副本。
copy()方法的语法如下:
dict.copy()
此语法中dict代表指定字典,该方法不需要参数。返回结果为一个字典的浅复制。该方法使用示例如下:
>>> student={'小萌': '1001', '小智': '1002', '小强': '1005','小张': '1006'}
>>> st=student.copy()
>>> print('复制后得到的st为:',st)
复制后得到的st为: {'小强': '1005', '小萌': '1001', '小智': '1002', '小张': '1006'}
由输出结果看到,使用copy方法可以将字典复制给另一个变量。
我们可以通过下面的示例了解什么是浅复制。
>>> student={'小智': '1002', 'info':['小张','1006','man']}
>>> st=student.copy()
>>> st['小智']='1005'
>>> print('更改copy 后的st为:',st)
更改copy 后的st为: {'info': ['小张', '1006', 'man'], '小智': '1005'}
>>> print('原字符串为::',student)
原字符串为:: {'info': ['小张', '1006', 'man'], '小智': '1002'}
>>> st['info'].remove('man')
>>> print('删除后st为:',st)
删除后st为: {'info': ['小张', '1006'], '小智': '1005'}
>>> print('删除后student为:',student)
删除后student 为: {'info': ['小张', '1006'], '小智': '1002'}
由输出结果看到,替换副本的值时原始字典不受影响。如果修改了某个值(原地修改,不是替换),原始字典就会改变,因为同样的值也在原字典中。以这种方式进行复制就是浅复制,而使用深复制(deep copy)可以避免该问题,此处不做讲解,有兴趣的读者可以自己查找相关资料。
3 fromkeys()方法
fromkeys()方法用于创建一个新字典,以序列seq中的元素做字典的键,value为字典所有键对应的初始值。
fromkeys()方法的语法如下:
dict.fromkeys(seq[, value]))
此语法中dict代表指定字典;seq代表字典键值列表;value代表可选参数,设置键序列(seq)的值。该方法返回结果为列表。
该方法使用示例如下:
>>> seq = ('name', 'age', 'sex')
>>> info = dict.fromkeys(seq)
>>> print ("新的字典为 : %s" % info)
新的字典为 : {'name': None, 'sex': None, 'age': None}
>>> info = dict.fromkeys(seq, 10)
>>> print ("新的字典为 : %s" % info)
新的字典为 : {'name': 10, 'sex': 10, 'age': 10}
由输出结果看出,fromkeys方法使用给定的键建立新字典,每个键默认对应的值为None。
4 get()方法
get()方法返回指定键的值,如果值不在字典中,就返回默认值。
get()方法的语法如下:
dict.get(key, default=None)
此语法中dict代表指定字典,key代表字典中要查找的键,default代表指定键的值不存在时返回默认值。该方法返回结果为指定键的值,如果值不在字典中,就返回默认值None。
该方法使用示例如下:
>>> student={'小萌': '1001', '小智': '1002'}
>>> print ('小萌的学号为: %s' % student.get('小萌'))
小萌的学号为: 1001
由输出结果看到,get方法使用起来比较简单。再看如下示例:
>>> st={}
>>> print(st['name'])
Traceback (most recent call last):
File "<pyshell#166>", line 1, in <module>
print(st['name'])
KeyError: 'name'
>>> print(st.get('name'))
None
>>> print('name 的值为:%s' % st.get('name'))
name 的值为:None
由输出结果看到,其他方法试图访问字典中不存在的项时会出错,而使用get方法就不会。使用get方法访问一个不存在的键时,返回None。这里可以自定义默认值,用于替换None,例如:
>>> st={}
>>> print('name 的值为:%s' % st.get('name','未指定'))
name 的值为:未指定
由输出结果看到,输出结果中用“未指定”替代了None。
5 key in dict方法
Python字典in操作符用于判断键是否存在于字典中,如果键在字典dict中就返回true,否则返回false。
该方法的语法如下:
key in dict
此语法中dict代表指定字典,key代表要在字典中查找的键。如果键在字典里就返回true,否则返回false。
该方法使用示例如下:
>>> student={'小萌': '1001', '小智': '1002'}
>>> print('小萌在student字典中:%s'%('小萌' in student))
小萌在student 字典中:True
>>> print('小强在student字典中:%s'%('小强' in student))
小强在student 字典中:False
由输出结果看到,已返回对应的True或False。
该方法是Python 3中的方法。在Python 2中有一个有相同功能的方法——has_key方法,has_key方法的使用方式和in不同。
6 items()方法
items()方法以列表返回可遍历的(键,值)元组数组。
items()方法的语法如下:
dict.items()
此语法中dict代表指定字典,该方法不需要参数。返回结果为可遍历的(键/值)元组数组。
该方法使用示例如下:
>>> student={'小萌': '1001', '小智': '1002'}
>>> print('调用items方法的结果:%s'% student.items())
调用items 方法的结果:dict_items([('小萌', '1001'), ('小智', '1002')])
由输出结果看到,返回结果为一个元组数组。
在Python 2中提供了一个iteritems方法,该方法和items方法的作用大致相同,但是iteritems方法返回的是一个迭代器对象,而不是列表。在Python 3中没有iteritems方法。
7 keys()方法
keys()方法以列表返回一个字典所有键。
keys()方法的语法如下:
dict.keys()
此语法中dict代表指定字典,该方法不需要参数。返回结果为一个字典所有键。
该方法使用示例如下:
>>> student={'小萌': '1001', '小智': '1002'}
>>> print('字典student所有键为:%s'% student.keys())
字典student 所有键为:dict_keys(['小萌', '小智'])
由输出结果看到,返回的是一个元组数组,数组中包含字典所有键。
8 setdefault()方法
setdefault()方法和get()方法类似,用于获得与给定键相关联的值。如果键不存在于字典中,就会添加键并将值设为默认值。
setdefault()方法的语法如下:
dict.setdefault(key, default=None)
此语法中dict代表指定字典,key代表查找的键值,default代表键不存在时设置的默认键值。该方法没有任何返回值。
该方法使用示例如下:
>>> student={'小萌': '1001', '小智': '1002'}
>>> print('小强的键值为:%s'% student.setdefault('小强'))
小强的键值为:None
>>> print('小智的键值为:%s'% student.setdefault('小智'))
小智的键值为:1002
>>> print('student 字典新值为:%s'% student)
student 字典新值为:{'小强': None, '小萌': '1001', '小智': '1002'}
由输出结果看到,当键不存在时,setdefault方法返回默认值并更新字典;如果键存在,就返回与其对应的值,不改变字典。和get一样,默认值可以选择,如果不设定就使用None,如果设定就使用设定的值,例如:
>>> student={'小萌': '1001', '小智': '1002'}
>>> print('小强的键值为:%s'% student.setdefault('小强'))
小强的键值为:None
>>> print('小zhang 的键值为:%s'% student.setdefault('小zhang','1006'))
小zhang 的键值为:1006
由输出结果看到,小强没有设置值,使用的是默认值,输出键值为None;小zhang设置的默认值是1006,输出键值为1006。
9 update()方法
update()方法用于把字典dict2的键/值对更新到dict里。
update()方法的语法如下:
dict.update(dict2)
此语法中dict代表指定字典,dict2代表添加到指定字典dict里的字典。该方法没有任何返回值。
该方法使用示例如下:
>>> student={'小萌': '1001', '小智': '1002'}
>>> student2={'小李':'1003'}
>>> print('原student 字典为:%s'% student)
原student字典为:{'小萌': '1001', '小智': '1002'}
>>> student.update(student2)
>>> print('新student 字典为:%s'% student)
新student字典为:{'小李': '1003', '小萌': '1001', '小智': '1002'}
>>> student3={'小李':'1005'}
>>> student.update(student3) #对相同项覆盖
>>> print('新student 字典为:%s'% student)
新student字典为:{'小李': '1005', '小萌': '1001', '小智': '1002'}
由输出结果看到,提供的字典中的项被添加到旧字典中,如果有相同的键就会覆盖。
10 values()方法
values()方法以列表形式返回字典中所有值。与返回键的列表不同,返回的列表中可以包含重复的元素。
values()方法的语法如下:
dict.values()
此语法中dict代表指定字典,该方法不需要参数。返回结果为字典中的所有值。
该方法使用示例如下:
>>> student={'小萌': '1001', '小智': '1002','小李':'1001'}
>>> print('student 字典所有值为:%s'% list(student.values()))
student 字典所有值为:['1001', '1001', '1002']
由输出结果看到,返回的列表中包含重复的元素。
调试
下面我们通过示例进行介绍,这里通过设置一些错误让读者认识在编写代码过程中的常见问题,以帮助读者熟悉和解决实际遇到的问题。
(1)使用列表根据姓名查找学号,学号使用字符串表示,如果更改为使用数字表示会如何?例如:
>>> students=['小萌','小智','小强','小张','小李']
>>> numbers=[1001,1002,1003,1004,1005]
>>> print('小智的学号是:',numbers[students.index('小智')])
小智的学号是: 1002
输出结果和使用字符串表示的输出结果没有什么不同。不过这里数字都是以1开头,若把1更改为0,我们试试:
>>> students=['小萌','小智','小强','小张','小李']
>>> numbers=[0001,0002,0003,0004,0005]
SyntaxError: invalid token
可以看出,numbers的编译不让通过,告诉我们这是一个无效标记。这就是不使用数字而使用字符串的原因,使用数字碰到以0开头的数字就会出现问题。
(2)尝试从字典中输出一个字符宽度为10的元素?例如:
>>> student={'小萌':'1001','小智':'1002','小强':'1003'}
>>> print('小萌的学号是:%(小萌)10s' % student) #字符宽度为10
小萌的学号是: 1001
尝试把10s变换为10d、10f、-10s、+10s,看看输出结果是怎样的。