dict 類型不但在各種程序里廣泛使用,它也是 Python 語言的基石。模塊的命名空間實(shí)例的屬性和函數(shù)的關(guān)鍵字參數(shù)中都可以看到字典的身影晰洒。
第三章 字典和集合
可散列的數(shù)據(jù)類型:
如果一個對象是可散列的,那么在這個對象的生命周期中啥箭,它的散列值是不變的谍珊,而且這個對象還需要實(shí)現(xiàn)__hash__()
方法,另外可散列對象還需要有__eq__()
方法急侥,這樣才能跟其他鍵做比較砌滞。
字典提供了很多種構(gòu)造方法:
a = dict(one=1, two=2)
b = {'one':1, 'two':2}
c = dict(zip(['one', 'two'], [1, 2]))
d = dict([('one', 1), ('two', 2)])
e = dict({'one': 1, 'two': 2})
>>> a == b == c == d == e
True
除了這些字面語法和靈活的構(gòu)造元素以外,字典推導(dǎo)也可以用來建造新的 dict:
a = ['s', 'o', 's']
b = {k: v for k, v in enumerate(a)}
>>> print(b)
{0: 's', 1: 'o', 2: 's'}
使用 setdefault
來處理找不到的鍵
當(dāng)字典 d[k] 不能找到正確的鍵時坏怪, Python 會拋出異常贝润,這個行為符合 Python 所信奉的”快速失敗“哲學(xué),我們都知道可以用 d.get(k, default)
來代替 d[k]
铝宵,給找不到的鍵一個默認(rèn)的返回值打掘。但是要更新某個鍵對應(yīng)的值時,不管使用 __getitem__
還是 get
都會不太自然鹏秋,而且效率低尊蚁。就像如下還沒有經(jīng)過優(yōu)化的代碼所顯示的那樣,dict.get
并不是處理找不到鍵的最好方式侣夷。
# -*- coding: utf-8 -*-
"""
@Desc : 獲取一段文本中的單詞出現(xiàn)的頻率以及出現(xiàn)的位置横朋,如(1, 2), 橫縱坐標(biāo)都以 1 開始
"""
import sys
import re
# 創(chuàng)建正則 Pattern 對象,確保進(jìn)行正則操作時不用重復(fù)創(chuàng)建 Pattern 對象
WORD_RE = re.compile(r'\w+')
index = {}
# sys.argv 是一個列表惜纸,sys.argv[0] 一般是"被調(diào)用的腳本名或全路徑"叶撒,sys.argv[1] 以及以后的都是傳入的參數(shù)
# 如 python main.py ../../data/zen.txt
# sys.argv[0] 就是 main.py
# sys.argv[1] 就是 ../../data/zen.txt
with open(sys.argv[1], encoding='utf-8') as fp:
# enumerate 有兩個參數(shù)绝骚,第一個參數(shù)是一個 Iterable(可迭代對象),第二個參數(shù) start 是開始位置祠够,默認(rèn)為 0
for line_no, line in enumerate(fp, 1):
# re.finditer(pattern, str) <=> pattern.finditer(str)压汪,返回一個iterator(迭代器)
for match in WORD_RE.finditer(line):
word = match.group()
column_no = match.start() + 1
location = (line_no, column_no)
# 提取 word 出現(xiàn)的情況,如果還沒有該記錄止剖,返回[], 這不是一種好的寫法
occurrences = index.get(word, []) # ①
# 將新單詞出現(xiàn)的位置添加到列表的后面
occurrences.append(location) # ②
# 將新列表放回到字典中穿香,這里又牽扯到一次查詢操作
index[word] = occurrences # ③
# sorted 函數(shù)的 key=參數(shù)沒有調(diào)用str.upper绎速,而是把這個方法的引用傳遞給 sorted 函數(shù)
# 這樣在排序的時候皮获,就會按照str.upper處理后的方式進(jìn)行排序纹冤。
for word in sorted(index, key=str.upper):
print(word, len(index[word]))
上述處理單詞的三行(① ② ③),通過 dict.setdefalut
可以只有一行就解決雁歌。
...
location = (line_no, column_no)
# 獲取單詞的出現(xiàn)情況列表,如果單詞不存在靠瞎,把單詞和一個空列表放進(jìn)映射求妹,
# 然后返回這個空列表,這樣就能在不進(jìn)行第二次查找的情況下更新列表了
index.setdefault(word, []).append(location)
...
即:
my_dict.setdefault(key, []).append(new_value)
<=>
if key not in my_dict:
my_dict[key] = []
my_dict[key].append(new_value)
只不過后者至少要進(jìn)行兩次鍵查詢丑勤,如果鍵不存在的話吧趣,就是三次,用 setdefault 只需要一次就可以完成整個操作强挫,以上是針對通過查找來插入新值時的最佳解決方式。
字典的變種
-
collections.OrderedDict
, 有序字典呆细,在添加鍵的時候會保持順序,因此鍵的迭代次序總是一致的八匠。OrderedDict 的 popitem 默認(rèn)刪除并返回的是字典里的最后一個元素趴酣,但是如果popitem(last=False)
則刪除并返回第一個被添加進(jìn)的元素岖寞。
-
collections.ChainMap
柜蜈,該類型可以容納數(shù)個不同的映射對象,在進(jìn)行鍵查找的時候淑履,這些對象會被當(dāng)做一個整體被逐步查找,直到鍵被站到位置狸吞。
-
collections.Counter
,該類型會給鍵準(zhǔn)備一個整數(shù)計(jì)算器捷绒,每次更新一個鍵的時候都會增加這個計(jì)數(shù)器。所以這個類型還可以用來給可散列對象計(jì)數(shù)贯要。Counter 實(shí)現(xiàn)了 + 和 - 運(yùn)算符用來合并記錄椭住,還有most_common([n])
,它會按照次序返回映射里最常見的 n 個鍵和他們的計(jì)數(shù)京郑。
-
collections.UserDict
,該類就是把標(biāo)準(zhǔn)的 dict 用純 Python 又實(shí)現(xiàn)了一遍跟狱,跟上述這些開箱即用的類型不同户魏, UserDict 是讓用戶繼承寫子類(自定義映射類型)的。
創(chuàng)建自定義映射類型
就創(chuàng)造自定義映射類型來說关翎,以 UserDict 為基類,總比以普通的 dict 為基類要來得方便纵寝。dict 會在某些方法的實(shí)現(xiàn)上走一些捷徑星立,導(dǎo)致我們不得不在它的子類中重寫這些方法葬凳。
UserDict 并不是 dict 的子類室奏,但是 UserDict 有一個叫作 data 的屬性,是 dict 的實(shí)例荐健,這個屬性實(shí)際上是 UserDict 最終存儲數(shù)據(jù)的地方琳袄。
'''
自定義字符串鍵字典
無論是添加,更新還是查詢操作址否,StrKey都會把非字符串的鍵轉(zhuǎn)換為字符串
'''
import collections
class StrKeyDict(collections.UserDict):
def __missing__(self, key):
if isinstance(key, str):
raise KeyError(key)
return self[str(key)]
def __contains__(self, key):
# 可以放心假設(shè)所有已經(jīng)存儲的鍵都是字符串碎紊,所以只要在self.data中查詢就好了
return str(key) in self.data
def __setitem__(self, key, item):
# self.data 為 dict 的實(shí)例
self.data[str(key)] = item
字典中的散列表
散列表:其實(shí)是一個稀疏數(shù)組(總有空白元素的數(shù)組稱為稀疏數(shù)組)。一般將散列表里的單元稱為表元音同,在 dict 的散列表中,每個鍵值對都占用一個表元权均,每個表元有兩個部分锅锨,分別是是對鍵、對值的引用必搞,因?yàn)樗斜碓拇笮∫恢拢钥梢酝ㄟ^偏移量來讀取某個表元塔橡。
因?yàn)?Python 會設(shè)法保證大概還有三分之一的表元是空的,所以在快要達(dá)到這個閥值的時候谱邪,原有的散列表會復(fù)制到一個更大的空間里庶诡。
字典的限制
- 鍵必須是可散列的
- 字典在內(nèi)存上的開銷巨大,由于散列表必須是稀疏的扯俱,這導(dǎo)致字典在空間上的效率低下,所以迅栅,如果你要存放數(shù)量巨大的記錄,那么放在由元組或是具名元組構(gòu)成的列表中會是比較好的選擇为流。
- 往字典里添加新鍵可能會改變已有鍵的順序让簿。無論何時往字典里添加新的鍵,Python 解釋器都可能做出為字典擴(kuò)容的決定尔当。擴(kuò)容導(dǎo)致的結(jié)果就是要新建一個更大的散列表,并把已有的元素添加到新表里锐帜,這個過程可能會發(fā)生新的散列沖突畜号,導(dǎo)致新建列表中鍵的次序變化。如果你在迭代一個字典的所有鍵的過程中同時對字典進(jìn)行修改简软,那么這個循環(huán)很有可能會跳過一些鍵——甚至是跳過那些字典中已經(jīng)有的鍵。
集合
集合字面量,{1}, {1, 2}贸典,但是如果是空集,必須寫成set()
的形式据过。
集合推導(dǎo),{ i for i in range(10)}
绳锅。
集合可用于去重酝掩。
集合的特點(diǎn):
- 集合里的元素必須是可散列的
- 集合很消耗內(nèi)存
- 可以很高效的判斷元素是否存在與某個集合
- 元素的次序取決于被添加到集合里的次序
- 往集合里添加元素,可能會改變集合里已有元素的次序