[Toc]
# 第7章 dict的使用
又是一個周末!最近墨博士一直出差,小墨也忙于做暑假作業,就沒能繼續學Python,這天,墨哥哥看小墨作業已經差不多做完了,就想著再給小墨講講Python的內容。墨大俠都講了哪些東西呢?一起來看看吧。
時間:周日上午
地點:小墨家
墨哥哥:小墨,今天呢我再給你補充一些Python的內容怎么樣?
小墨:太好啦!我幾天沒看Python,都有點想它了。
墨哥哥:好,那我以咱們家的一些書為例,來給你說說Python中dict數據類型的用法。
# [插畫:書架,類似下圖]

## 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 角色選擇功能數據準備
墨哥哥:現在我們來編寫一個程序,綜合練習下這兩者相結合的用法。就來做個墨家村英雄榜吧。
# [插畫:墨家村英雄榜,類似下圖:]

小墨:墨家村英雄榜是什么,一個新游戲嗎?
墨哥哥:很多角色扮演游戲中,用戶每次可以選擇不同角色,比如三國類的游戲中這一次你可以扮演諸葛亮,下一次你就可以扮演曹操了。我們要做的這個墨家村英雄榜就是模擬游戲中玩家挑選角色的功能。
小墨:哦,我懂了。用字典存儲角色的信息然后供玩家去挑選。
墨哥哥:真是聰明的小墨!下面我們開始。先來將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的相互嵌套,對你來說可能有點復雜,但同時也說明了你已經成長了很多不是嗎。加油!學的越多,能做出的程序也就越好玩,越強大!