學(xué)習(xí)了 Python 基本的字典操作后,學(xué)習(xí)這些進(jìn)階操作,讓寫出的代碼更加優(yōu)雅簡潔和 pythonic 。
與字典值有關(guān)的計(jì)算
問題
想對(duì)字典的值進(jìn)行相關(guān)計(jì)算,例如找出字典里對(duì)應(yīng)值最大(最小)的項(xiàng)。
解決方案一:
假設(shè)要從字典 {'a':3, 'b':2, 'c':6}
中找出值最小的項(xiàng),可以這樣做:
>>> d = {'a':3, 'b':2, 'c':6}
>>> min(zip(d.values(), d.keys()))
(2, 'b')
值得注意的是 d.values()
獲取字典的全部值,d.keys()
獲取字典的全部鍵,而且兩個(gè)序列的順序依然保持一一對(duì)應(yīng)的關(guān)系。因此 zip(d.values(), d.keys())
實(shí)質(zhì)上生成的是一個(gè) (value, key)
的序列。min
函數(shù)通過比較序列中的元組 (value, key)
找出其最小值。
解決方案二:
除了使用 zip(d.values(), d.keys())
外,還可以使用 dict.items()
方法和生成器推導(dǎo)式來生成 (value, key)
序列,從而傳遞給 min
函數(shù)進(jìn)行比較:
>>> d = {'a':3, 'b':2, 'c':6}
>>> min((v ,k) for (k, v) in d.items())
(2, 'b')
這里 min
函數(shù)的參數(shù) (v ,k) for (k, v) in d.items()
其實(shí)是一個(gè)生成器推導(dǎo)式(和列表推導(dǎo)式一樣,只是把列表推導(dǎo)式的 []
改為 ()
,而且其返回的一個(gè)生成器而非列表),由于生成器推導(dǎo)式做為 min
函數(shù)的參數(shù),所以可以省略掉兩邊的括號(hào)(不做為參數(shù)時(shí)寫法應(yīng)該是 ((v ,k) for (k, v) in d.items())
)。
字典推導(dǎo)式
問題
想把一個(gè)元組列表轉(zhuǎn)換成一個(gè)字典,例如把 [('a', 1), ('b', 2), ('c', 3)]
轉(zhuǎn)化為 {'a': 1, 'b': 2, 'c': 3}
解決方案
類似于列表推導(dǎo)式,字典推導(dǎo)式可以方便地從其他數(shù)據(jù)結(jié)構(gòu)構(gòu)造字典,例如:
>>> l = [('a', 1), ('b', 2), ('c', 3)]
>>> {k: v for k, v in l}
{'c': 3, 'b': 2, 'a': 1}
字典推導(dǎo)式的規(guī)則和列表推導(dǎo)式一樣,只是把 []
換成 {}
尋找字典的交集
問題
假設(shè)有兩個(gè)字典:
d1 = {'a':1, 'b':2, 'c':3, 'd':4}
d2 = {'b':2, 'c':3, 'd':3, 'e':5}
要找出這兩個(gè)字典中具有公共鍵的項(xiàng),即要得到結(jié)果 {'b':2, 'c':3}
解決方案
我們知道一般通過 d.items()
方法來遍歷字典,d.items()
方法返回的對(duì)象是一個(gè)類集合對(duì)象,支持集合的基本運(yùn)算,如取交集、并集等。
>>> dict(d1.items() & d2.items()) # 取交集
{'b': 2, 'c': 3}
此外,d.keys()
返回字典的鍵,也是一個(gè)類集合對(duì)象,如果我們只想找出兩個(gè)字典中鍵相同的項(xiàng),可以這樣:
>>> { k:d1[k] for k in d1.keys() & d2.keys() }
{'b': 2, 'd': 4, 'c': 3}
這里如果相同的鍵對(duì)應(yīng)不同的值則去第一個(gè)字典中的值。推廣開來,如果想排除掉字典中的某些鍵,可以這樣:
>>> { k:d1[k] for k in d1.keys() - {'c', 'd'} } # - 號(hào)的含義是集合的差集操作
{'b': 2, 'a': 1}
但有一點(diǎn)需要注意的是,d.values()
返回字典的值,由于字典對(duì)應(yīng)的值不一定唯一,所以 d.values()
一般無法構(gòu)成一個(gè)集合,因此也就不支持一般的集合操作。
多個(gè)字典連接成一個(gè)字典
問題
有多個(gè)字典,例如:
d1 = {'a':1, 'b':2, 'c':3}
d2 = {'c':4, 'd':5, 'e':6}
想將這多個(gè)字典連接為一個(gè)字典,或一次性對(duì)多個(gè)字典進(jìn)行迭代操作。
解決方案
使用 collections.ChainMap
:
>>> from collections import ChainMap
>>> chain_dict = ChainMap(d1, d2)
>>> for k, v in chain_dict.items():
print(k, v)
a 1
e 6
d 5
c 3
b 2
ChainMap
將傳入的多個(gè)字典連接為一個(gè)字典,并返回一個(gè) ChainMap
對(duì)象,這個(gè)對(duì)象的行為就像一個(gè)單一的字典,我們可以對(duì)其進(jìn)行取值或者迭代等操作。注意到這里鍵 c
對(duì)應(yīng)的值為 3,如果傳入 ChainMap
的字典含有相同的鍵,則對(duì)應(yīng)的值為先傳入的字典中的值。
此外,如果你只想單純地迭代字典的鍵值對(duì),可以結(jié)合使用 items()
和 itertools.chain()
方法:
>>> from itertools import chain
>>> for k, v in chain(d1.items(), d2.items()):
print(k, v)
a 1
c 3
b 2
e 6
c 4
d 5
這里相同的鍵會(huì)被分別迭代出來。
保持字典有序
問題
想讓字典中元素的迭代順序和其加入字典的順序保持一致
解決方案
通常來說,使用 d.items()
或者 d.keys()
、d.values()
方法迭代出來的元素順序是無法預(yù)料的。例如對(duì)字典 d = {'a':1, 'b':2, 'c':3}
迭代:
>>> d = dict()
>>> d['a'] = 1
>>> d['b'] = 2
>>> d['c'] = 3
>>> for k, v in d.items():
print(k, v)
a 1
c 3
b 2
每一次運(yùn)行結(jié)果都可能不同。如果想讓元素迭代的順序和創(chuàng)建字典時(shí)元素的順序一致,就要使用 collections.OrderedDict
代替普通的 dict
:
>>> from collections import OrderedDict
>>> ordered_d = OrderedDict()
>>> ordered_d['a'] = 1
>>> ordered_d['b'] = 2
>>> ordered_d['c'] = 3
>>> for k, v in ordered_d.items():
print(k, v)
a 1
b 2
c 3
OrderedDict
實(shí)際通過維護(hù)一個(gè)雙向鏈表來記錄元素添加的順序,因此其耗費(fèi)的內(nèi)存大約為普通字典的兩倍。所以在實(shí)際使用中需綜合考慮各種因素來決定是否使用 OrderedDict
。
使字典的鍵映射多個(gè)值
問題
通常情況下字典的鍵只對(duì)應(yīng)一個(gè)值。現(xiàn)在想讓一個(gè)鍵對(duì)應(yīng)多個(gè)值。
解決方案
為了使一個(gè)鍵對(duì)應(yīng)多個(gè)值,首先需要把多個(gè)值放到一個(gè)容器中(例如列表或者集合等)。例如有這樣一個(gè)列表:[('a', 1), ('a', 2), ('b', 3), ('b', 4), ('c', 5)]
,我們要將其轉(zhuǎn)換成一個(gè)字典,保持元素的鍵值對(duì)應(yīng)關(guān)系,通常我們會(huì)寫這樣的代碼:
>>> from pprint import pprint
>>> l = [('a', 1), ('a', 2), ('b', 3), ('b', 4), ('c', 5)]
>>> d = {}
>>> for k, v in l:
if k in d:
d[k].append(v)
else:
d[k] = [v]
>>> pprint(d)
{'a': [1, 2], 'b': [3, 4], 'c': [5]}
但是 if else
語句讓代碼顯得有點(diǎn)冗余和不易讀,Python
的 defaultdict
改善上述代碼。
>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> for k, v in l:
d[k].append(v)
>>> pprint(d)
defaultdict(<class 'list'>, {'c': [5], 'b': [3, 4], 'a': [1, 2]})
if else
的判語句沒有了。
defaultdict
是 dict
的一個(gè)子類。對(duì) dict
來說,如果 key
不存在,則 dict[key]
取值操作會(huì)拋出 KeyError
異常,但是 defaultdict
則會(huì)返回一個(gè)傳入 defaultdict
構(gòu)造器的類的實(shí)例(例如一個(gè)列表)或者自定義的缺失值。因此在上例中,對(duì)于 d[k].append(v)
,當(dāng) k
不存在時(shí),則會(huì)先執(zhí)行 d[k] = []
并返回這個(gè)空列表,繼而將 v
加入到列表中。
傳入 defualtdict
構(gòu)造器的值不一定要是一個(gè)類,也可以是一個(gè)可調(diào)用的函數(shù),當(dāng)相應(yīng)的鍵不在 defualtdict
中時(shí),其默認(rèn)的值就為這個(gè)函數(shù)的返回值,例如:
>>> from collections import defaultdict
>>> def zero_default():
return 0
>>> d = defaultdict(zero_default)
>>> d['a'] = 1
>>> d['a']
1
>>> d['b']
0
>>> d.keys()
dict_keys(['b', 'a'])
>>>
利用這樣一個(gè)特性,我們可以構(gòu)造無限深度的字典結(jié)構(gòu):
>>> from collections import defaultdict
>>> import json
>>> tree = lambda: defaultdict(tree)
>>> d = tree()
>>> d['a']['b'] = 1
>>> print(json.dumps(d)) # 為了顯示的格式更好看
{"a": {"b": 1}}
這里當(dāng)執(zhí)行 d['a']
時(shí),由于相應(yīng)的鍵不存在,故返回一個(gè) defaultdict(tree)
,當(dāng)再執(zhí)行 d['a']['b'] = 1
時(shí),將鍵 b
對(duì)應(yīng)的值設(shè)為 1 。