[Toc]
# 第7章 dict的使用
又是一个周末!最近墨博士一直出差,小墨也忙于做暑假作业,就没能继续学Python,这天,墨哥哥看小墨作业已经差不多做完了,就想着再给小墨讲讲Python的内容。墨大侠都讲了哪些东西呢?一起来看看吧。
时间:周日上午
地点:小墨家
墨哥哥:小墨,今天呢我再给你补充一些Python的内容怎么样?
小墨:太好啦!我几天没看Python,都有点想它了。
墨哥哥:好,那我以咱们家的一些书为例,来给你说说Python中dict数据类型的用法。
# [插画:书架,类似下图]
![](https://img.kancloud.cn/7e/0c/7e0cb84d5a3270faa877613c2054277b_1200x808.png)
## 7.1 为什么要使用dict
墨哥哥:比如现在有两个list:
```
list1 = ['朝花夕拾', '繁星春水', '骆驼祥子', '西游记', '水浒传', '三国演义']
list2 = ['鲁迅', '冰心', '老舍', '吴承恩', '施耐庵', '罗贯中']
```
两个list分别存储名著和名著的作者,并且名著和其对应作者在list中的顺序一致。
此时想查找“骆驼祥子”的作者是谁,应该如何做?
小墨:嗯……,因为名著和其作者在两个list中的顺序一致,可以先找到“骆驼祥子”在“list1”中的顺序,也就是下标,然后根据此下标从“list2”中取作者,就是“骆驼祥子”的作者啦。
墨哥哥:思路对了。参考代码如下:
```
for i in range(len(list1)):
if list1[i] == '骆驼祥子':
print(list2[i])
```
注意这里的字符串是否相等的比较,也使用==就行。
接下来,考你几个问题,听好了小墨。
如果要添加新的名著和作者进来,比如添加“红楼梦”和“曹雪芹”进来,如何做?
小墨:list1中添加名著,list2中对应添加作者。
墨哥哥:那如果想删除某个名著呢?
小墨:list1中删除名著,list2中删除作者。
墨哥哥:添加时,如果添加名著成功了,但是添加作者失败了,或者删除时,删除名著成功了,但是删除作者失败了,怎么办?
小墨:……
墨哥哥:这样的话,最大的问题就是原本顺序一一对应的两个列表,一下子全乱掉了。比如原本两个列表中都有1000个值,现在要删除第8个,名著删除成功,list1剩999个数据,作者删除失败list2中还是1000个数据,此时从第8个开始往后的每一个数据都没法再一一对应上了,如果类似这样的问题出现了几次,是不是就全乱套了?
小墨:确实,下标乱了就没法对应上了。这个问题如何解决呢?
墨哥哥:如果两个列表中的数据能统一操作,比如删除成功就都成功,失败就都失败,就好了。
小墨:好了墨哥哥,别卖关子了,赶紧说说怎么解决吧。
墨哥哥:这个可以使用Python中的dict类型来解决。
## 7.2 dict的写法
墨哥哥:dict,全称是dictionary,字典的意思。这是Python内置的一种数据类型,它不同于list的存储一列数据,它存储两列数据,这也是它能够使数据要么全成功要么全失败的秘诀。
下面我们定义一个dict:
```
book_author_dict = {}
```
这样就定义好了。“book_author_dict”是我们定义的dict类型的变量的名称,后面跟一对大括号。大括号中可以放入要存储的数据,现在什么都没有,称为空dict。
放入数据时的定义如下:
```
book_author_dict = {'朝花夕拾': '鲁迅'}
```
此时“{'朝花夕拾': '鲁迅'}”就是字典的内容,大括号中的数据组成“key:value”的形式,key和value之间用英文冒号“:”分割,这里的key称为dict的“键”,value被称为dict的“值”,两个放在一块称为“键值对”。一个dict中可以放多个键值对,键值对和键值对之间用逗号“,”分隔,和list中类似,最后一个键值对后面的逗号可以有也可以没有。比如:
```
book_author_dict = {
'朝花夕拾': '鲁迅',
'繁星春水': '冰心',
'骆驼祥子': '老舍',
'西游记': '吴承恩',
'水浒传': '施耐庵',
'三国演义': '罗贯中'
}
```
这就定义好了一个dict,它由6对键值对组成。
小墨:哦,那这个dict如何添加或删除新数据呢?
墨哥哥:别着急。还记得昨天我跟你说过的list中的数据管理方法有哪些吗?
小墨:增加、插入、删除、修改和查询。
墨哥哥:嗯增加和插入都是增加,所以对数据的管理无外乎就是增删改查了。今天我也从这几个方面来介绍一下dict的用法。
## 7.3 dict的用法
### 7.3.1 单个查找
墨哥哥:dict中的查找,是根据key查找value,可以通过dict[key]的格式取得value,例如:
```
# 查找朝花夕拾的作者
print(book_author_dict['朝花夕拾'])
```
输出:
```
鲁迅
```
也可以通过dict.get(key)的方式取得value:
```
print(book_author_dict.get('朝花夕拾'))
```
当然,如果key不存在,则查找会报错。
### 7.3.2 添加
墨哥哥:dict的添加格式是:dict[key] = value,直接将value赋值一个新的key就可以了,如:
```
# 添加新的键值对
book_author_dict['红楼梦'] = '曹雪芹'
print(book_author_dict)
```
直接输出dict本身,输出结果为:
```
{'朝花夕拾': '鲁迅', '繁星春水': '冰心', '骆驼祥子': '老舍', '西游记': '吴承恩', '水浒传': '施耐庵', '三国演义': '罗贯中', '红楼梦': '曹雪芹'}
```
### 7.3.3 修改
墨哥哥:如果一个key已经存在,这时又给这个key匹配了一个新的value,那么原键值对会被覆盖,这个可以看做是修改功能,比如将朝花夕拾的作者由鲁迅改为周树人:
```
book_author_dict['朝花夕拾'] = '周树人'
print(book_author_dict)
```
输出:
```
{'朝花夕拾': '周树人', '繁星春水': '冰心', '骆驼祥子': '老舍', '西游记': '吴承恩', '水浒传': '施耐庵', '三国演义': '罗贯中', '红楼梦': '曹雪芹'}
```
从这里我们还可以得出一个结论:**dict中的key不可能重复(重复的被覆盖掉了)**
### 7.3.4 删除
墨哥哥:dict中删除也使用pop()函数,括号中传入key,也即根据key删除整个键值对,如:
```
# 删除红楼梦这个key和曹雪芹这个value
book_author_dict.pop('红楼梦')
print(book_author_dict)
```
输出:
```
{'朝花夕拾': '周树人', '繁星春水': '冰心', '骆驼祥子': '老舍', '西游记': '吴承恩', '水浒传': '施耐庵', '三国演义': '罗贯中'}
```
这样就达到了key和value一对数据一删都删的目的。
### 7.3.5 查找全部的key
墨哥哥:除了上面的增删改查,dict经常会需要查询全部的key。配合for循环,查找全部的名著名称的方法为:
```
for book in book_author_dict:
print(book)
```
### 7.3.6 查找所有key和value
墨哥哥:如果想查找dict全部的key和value,比如这里的查询全部的名著和作者,则需要使用dict的items()方法,items()方法会得到所有的键值对,我们使用for循环把这些键值对一个一个的取出就可以了。
```
for book, author in book_author_dict.items():
print('著作:%s,作者:%s' % (book, author))
```
好,以上呢就是dict的常见基础用法,怎么样小墨?
小墨:内容都不难,但是我得自己做一遍,加深印象。
墨哥哥:编程就是多多练习。整好我准备了一道题,你来试试吧,做完之后我再带你做个有意思的程序怎么样?
小墨:好啊好啊,我喜欢墨哥哥的游戏。
墨哥哥:好,那先来完成下面的练习吧。
> 练习:查找冰心的著作是什么
小墨:dict中并没有根据value查找key的方法,而是只能根据key查找value,现在要想根据作者查找著作,可以先把原dict中的key和value全部取出来,然后value做key,key做value构建一个新的dict,在新的dict中就可以根据作者查找其著作了。
代码如下:
```
# 图书和作者的字典
book_author_dict = {
'朝花夕拾': '鲁迅',
'繁星春水': '冰心',
'骆驼祥子': '老舍',
'西游记': '吴承恩',
'水浒传': '施耐庵',
'三国演义': '罗贯中'
}
# 定义一个空的字典,
author_book_dict = {}
# 交换key和value组成新的字典
for book, author in book_author_dict.items():
author_book_dict[author] = book
# 查找冰心的著作名称
print(author_book_dict['冰心'])
```
## 7.4 list和dict的综合运用
墨哥哥:小墨,前面你已经学习了dict的基础用法,学的不错。现在呢再来看一个复杂的情况:
以
```
book_author_dict = {'朝花夕拾': '鲁迅'}
```
为例,dict由key和value构成,也即dict只能存储两列数据,那如果我想存储多列数据,比如书的价格,书的出版社等也想存起来,该怎么办呢?
可以调整一下思路,不再是key存书名,value存作者名,而是将书所有的信息全部作为value,然后给这些信息取个key对应,就像这样:
```
book_dict = {'name': '朝花夕拾', 'author': '鲁迅', 'price': '39.9¥', 'publishing': '北京未名出版社'}
```
这样的话,一个dict就表示一本书的相关信息。
此时问题就来了:一个dict表示一本书,那如何表示多本书呢?
### 7.4.1 list和dict的嵌套表示
墨哥哥:还记得前面说的list吗?list用于存储一系列的数据,可以把每本书的信息看做是一个整体,然后存到list中来,其大致结构如下:
```
# 第一本书的信息
book_dict_1 = {}
# 第二本书的信息
book_dict_2 = {}
book_dict_3 = {}
... # 很多本书的信息
# 每本书的信息看做一个整体存在list中。
list = [book_dict_1, book_dict_2, book_dict_3...]
```
这就是dict作为了list的元素,当然,还能再复杂一点,我们结合具体案例来说。
### 7.4.2 角色选择功能数据准备
墨哥哥:现在我们来编写一个程序,综合练习下这两者相结合的用法。就来做个墨家村英雄榜吧。
# [插画:墨家村英雄榜,类似下图:]
![](http://7xtc8l.com1.z0.glb.clouddn.com/20180110151738.png)
小墨:墨家村英雄榜是什么,一个新游戏吗?
墨哥哥:很多角色扮演游戏中,用户每次可以选择不同角色,比如三国类的游戏中这一次你可以扮演诸葛亮,下一次你就可以扮演曹操了。我们要做的这个墨家村英雄榜就是模拟游戏中玩家挑选角色的功能。
小墨:哦,我懂了。用字典存储角色的信息然后供玩家去挑选。
墨哥哥:真是聪明的小墨!下面我们开始。先来将1个角色的数据表示出来,我们以墨小小这个角色为例。
小墨:我又成游戏的主角了。
墨哥哥:还有你的小伙伴呢。
以墨小小为例,他有4个技能,这4个技能可以存成list也可以存成dict,这里我们存成list吧:
```
skills = ['一墨横空', '墨渡迷津', '墨之纵横', '墨下乾坤']
```
然后是墨小小的基本信息,如姓名、生命值、攻击力防御力等,可以定义普通变量表示,我们随便给个数值,如下:
```
name = '墨小小' # 姓名
hp = 1000 # 血量
mp = 800 # 魔法量
ap = 45 # 攻击力
dp = 20 # 防御力
```
如果用一个dict表示墨小小,也即既有墨小小的基本信息,又有墨小小的技能列表,可以这样做:
```
hero = {
'name': '墨小小',
'hp': 1000,
'mp': 800,
'ap': 45,
'dp': 20,
'skills': ['一墨横空', '墨渡迷津', '墨之纵横', '墨下乾坤'],
}
```
这就是将list作为dict的元素了。
小墨:哦,原来dict中的value可以是不同的类型,并且还可以是list类型呢。
墨哥哥:是的。好了,上面是墨小小一个人的信息,如果想表示多个人呢?就是将上述dict作为一个整体看,放入list中,如下:
```
hero1 = {
'name': '墨小小',
'hp': 1000,
'mp': 800,
'ap': 45,
'dp': 20,
'skills': ['一墨横空', '墨渡迷津', '墨之纵横', '墨下乾坤'],
}
hero2 = {
'name': '墨小妹',
'hp': 800,
'mp': 1000,
'ap': 50,
'dp': 18,
'skills': ['貂蝉拜月', '西施捧心', '昭君出塞', '贵妃醉酒'],
}
hero_list = [hero1, hero2]
```
或者直接一步到位,省去定义变量的麻烦:
```
hero_list = [{
'name': '墨小小',
'hp': 1000,
'mp': 800,
'ap': 45,
'dp': 20,
'skills': ['一墨横空', '墨渡迷津', '墨之纵横', '墨下乾坤'],
}, {
'name': '墨小妹',
'hp': 800,
'mp': 1000,
'ap': 50,
'dp': 18,
'skills': ['貂蝉拜月', '西施捧心', '昭君出塞', '贵妃醉酒'],
}]
```
这时就是list中包含dict,dict中还包含有list的结构了。
小墨:这样来看结构还挺复杂的。
墨哥哥:嗯。游戏中的角色一般都会按照职业分类,比如战士、法师等。接下来我们先来开发分类查找的功能,也即能在所有角色中把全部战士找出来,或把全部法师找出来等。
小墨:那要如何才能知道哪些角色是同一类的呢?
'is_warrior': False, 'is_mage': False, 'is_hunter'
墨哥哥:问的好!为了能够分类,我们需要将每个角色所属的职业标示出来,可以用is_warrior表示是否是战士,is_mage表示是否是法师,is_hunter表示是否是猎人,战士、法师和猎人都是角色的种类。单个角色的数据如下:
```
{
'name': '墨小小',
'hp': 1000,
'mp': 800,
'ap': 45,
'dp': 20,
'skills': ['一墨横空', '墨渡迷津', '墨之纵横', '墨下乾坤'],
'is_warrior': True,
'is_mage': False,
'is_hunter': False
}
```
小墨:哦,这样一个角色就能属于多个分类了,如即使坦克又是战士。
墨哥哥:是的。接着我们定义5个角色出来:
```
hero_list = [
{'name': '墨小小', 'hp': 1000, 'mp': 800, 'ap': 45, 'dp': 20, 'skills': ['一墨横空', '墨渡迷津', '墨之纵横', '墨下乾坤'],
'is_warrior': True, 'is_mage': False, 'is_hunter': False, },
{'name': '墨小妹', 'hp': 1200, 'mp': 700, 'ap': 35, 'dp': 21, 'skills': ['貂蝉拜月', '西施捧心', '昭君出塞', '贵妃醉酒'],
'is_warrior': True, 'is_mage': True, 'is_hunter': False, },
{'name': '墨大元', 'hp': 1100, 'mp': 600, 'ap': 38, 'dp': 17, 'skills': ['千里横行', '寒刀断水', '狂龙破日', '天地无情'],
'is_warrior': True, 'is_mage': False, 'is_hunter': True, },
{'name': '墨当归', 'hp': 900, 'mp': 1100, 'ap': 44, 'dp': 17, 'skills': ['流水行云', '披云戴月', '翻云覆雨', '排山倒海'],
'is_warrior': False, 'is_mage': True, 'is_hunter': False, },
{'name': '墨鱼儿', 'hp': 1000, 'mp': 1000, 'ap': 42, 'dp': 23, 'skills': ['小楫轻舟', '扁舟一叶', '大江似练', '沧波万顷'],
'is_warrior': False, 'is_mage': False, 'is_hunter': True, }
]
```
小墨:哇!小妹的技能好厉害!
### 7.4.3 查找所有战士的姓名
墨哥哥:接下来说说功能。我们知道,使用for循环可以把list中的每个元素都拿出来,方法是使用for x in list,这里得到的每个x就是list中的每个元素。
现在你看,hero_list就是一个list,我们当然可以使用for循环拿出它的每一个元素。拿出来的每一个元素是什么呢?
小墨:hero_list中存的是5个角色,拿出的元素就是一个个的角色吧?
墨哥哥:是的,而每一个角色的数据本身又是一个dict,也即拿出来的是5个dict。
针对每一个dict来说,我们可以通过is_warrior这个key取对应的value,如果是True,说明这个角色是战士,此时我们再次通过name这个key取出它的名字就可以了。代码如下:
```
for x in hero_list:
if x.get('is_warrior'):
print(x.get('name'))
```
输出结果为:
```
墨小小
墨小妹
墨大元
```
小墨:哈哈墨妹妹也是战士了。
墨哥哥:自己编写的程序自己做主!
### 7.4.4 获取所有战士的技能列表
墨哥哥:现在我们来获取所有战士的技能列表。
上面的代码中x.get('name')就获取了所有战士的名字,那么我们把这里的name改成skills就可以获取所有战士的技能列表了。
```
for x in hero_list:
if x.get('is_warrior'):
print(x['skills']) # get或[],都可以通过key取value
```
但是,此时x['skills']拿到的技能列表是个list类型,也即“['一墨横空', '墨渡迷津', '墨之纵横', '墨下乾坤']”,如果是想拿到具体的一个个技能,还需要对这个list再次循环取出。程序变成了:
```
for x in hero_list:
if x.get('is_warrior'):
print('*' * 10)
print(x.get('name') + '的技能有:')
for skill in x['skills']:
print(skill)
```
输出结果为:
```
**********
墨小小的技能有:
一墨横空
墨渡迷津
墨之纵横
墨下乾坤
**********
墨小妹的技能有:
貂蝉拜月
西施捧心
昭君出塞
贵妃醉酒
**********
墨大元的技能有:
千里横行
寒刀断水
狂龙破日
天地无情
```
这里的“print('\*' * 10)”作用是输出10个星号,分割每个角色的技能列表,这样方便我们观察输出。你看,Python中的乘法不光是数学上的相乘,还能用于字符串和数字相乘,表示复制出来多少份字符串。
现在的程序就写成了for循环里嵌套了for循环,也称为双重for循环。小墨,你之前见过双重for循环吗?
小墨:没有。
墨哥哥:那看下这个案例,能更好的理解双重for循环的执行过程:
```
for i in ['a', 'b', 'c']:
for j in ['1', '2', '3']:
print(i + j)
```
运行输出结果为:
```
a1
a2
a3
b1
b2
b3
c1
c2
c3
```
你试着来分析一下吧。
小墨:我看看……,首先,外层的for循环会从['a', 'b', 'c']中取出a赋值给i,然后执行后面跟的代码块,现在整个内部for循环充当了代码块,所以会执行内部for循环。内部for循环是从['1', '2', '3']中依次取出每一个元素,通过+号跟i拼接,就会得到a1、a2和a3,此时内部for循环运行结束,也即外部for循环的第一次循环结束。程序进入下一次的外部循环中,i被赋值为了b,然后再次执行内部循环。依次类推,最终输出就是上面那种了。
墨哥哥:嗯分析清楚这个之后再看上面获取角色列表的程序就会清晰多了。双重for循环的作用就是将所有符合条件的角色的技能列表输出了出来。
### 7.4.5 根据输入查找某个角色的血量
墨哥哥:再来看最后一个功能:根据输入的姓名查找某个角色的血量,这个怎么做呢?
小墨:可以将用户输入的姓名和所有的角色姓名做比对,如果有名字一样的,就把对应的血量输出出来:
```
name = input('请输入要查找的英雄的姓名:')
for x in hero_list:
if x.get('name') == name:
print(name + '的血量为:%d' % x.get('hp'))
```
输出:
```
请输入要查找的英雄的姓名:墨大元
墨大元的血量为:1100
```
墨哥哥:问题是,有些搜索,需要能够模糊查询。所谓模糊查询,就比如我输入“大”,应该把“墨大元”给找出来,显然这里的条件判断x.get('name') == name是精确匹配,也即要查找“墨大元”必须输入“墨大元”才查找的出来。
小墨:那如何才能模糊查询呢?
墨哥哥:如果我们能判断一个字符串中是否包含另一个字符串就行了。这在Python中有很多种方法,常用的有:
1、字符串中的“in”操作,如a in b,表示a是否在b中,也即b是否包含a,返回True或False
```
name = '墨大元'
if '大' in name:
print('匹配')
```
2、字符串可以看作是字符的列表,也有对应下标的概念。可以使用find()方法,查找字符串中是否有某个子字符串,该方法返回该子字符串在字符串中出现的位置(下标)
如'abc'.find('a')得到0,'abc'.find('b')则得到1,如果找不到,则方法返回-1。使用find()来模糊查询代码如下:
```
name = '墨大元'
if name.find('大') != -1:
print('匹配')
```
3、和find()类似,也可以通过index()方法,该方法专门用于查找子字符串的下标,如果找不到则返回-1,index使用如下:
```
name = '墨大元'
if name.index('大') != -1:
print('匹配')
```
墨哥哥提醒:find和index都要和-1比较。因为子字符串有可能出现在第一位,此时下标为0,Python中0作为条件时就表示False。
小墨,使用上面三种方法中的任意一种,为上述小助手提供模糊查询功能吧。
> 动动手:请计算上述五种英雄的平均血量,注意避免“hard coding”
## 7.5 本章小结
墨哥哥总结:今天你主要学习了dict的基本用法,和list一样,基本用法也是增删改查。其中查询可能是根据某一key查找对应value;也可能是查询所有的key;也有可能是查询所有的key和value。之后做了一个墨家村英雄榜的程序。这个案例是dict和list的相互嵌套,对你来说可能有点复杂,但同时也说明了你已经成长了很多不是吗。加油!学的越多,能做出的程序也就越好玩,越强大!