「Python3學(xué)習(xí)筆記」讀書筆記—字典

字典是內(nèi)置類型中唯一的映射(Mapping)結(jié)構(gòu)矛缨,基于哈希表存儲鍵值對數(shù)據(jù)而昨。

值可以是任意類型的數(shù)據(jù)帆焕,但主鍵必須是可哈希的類型惭婿。常見的可變類型,如列表叶雹、集合等都不能作為主鍵使用财饥。即便是元組等不可變類型,也不能引用可變類型元素折晦,即元組中不能含有可變類型的元素钥星。

>>> import collections
>>> issubclass(list, collections.Hashable)
False
>>> issubclass(int, collections.Hashable)
True

>>> hash((1, 2, 3))
2528502973977326415
>>> hash((1, 2, [1, 2]))        # 包含可變類型
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

哈希計(jì)算通過調(diào)用 __hash__ 方法返回整數(shù)值,用來快速比較內(nèi)容是否相同满着。某些類型雖然有該方法谦炒,但實(shí)際無法執(zhí)行,故不能作為主鍵使用风喇。另外宁改,主鍵對象的哈希值必須恒定不變,否則無法查找鍵值魂莫,甚至?xí)l(fā)錯誤还蹲。

>>> callable(list().__hash__)
False

自定義類型默認(rèn)實(shí)現(xiàn)了 __hash__ 和 __eq__ 方法,用與哈希和相等比較操作耙考。前者為每個實(shí)例返回隨機(jī)值谜喊;后者除非與自己比較,否則總是返回 False琳骡。這兩個方法可根據(jù)需要進(jìn)行重載锅论。

作為常用的數(shù)據(jù)結(jié)構(gòu),又因?yàn)槊臻g的緣故楣号,字典的使用頻率非常高最易。Python 開發(fā)團(tuán)隊(duì)也一直致力于改進(jìn)其數(shù)據(jù)結(jié)構(gòu)和算法怒坯,這其中自然也包括慣用的緩存復(fù)用。

Python 3.6 借鑒 PyPy 字典設(shè)計(jì)藻懒,采用更緊湊的存儲結(jié)構(gòu)剔猿。keys.entries 和 values 用數(shù)組按添加順序存儲主鍵和值引用。實(shí)際哈希表由 keys.indices 數(shù)組承擔(dān)嬉荆,通過計(jì)算主鍵哈希值找到合適的位置归敬,然后在該位置存儲主鍵在 key.entries 的實(shí)際索引。如此一來鄙早,只要通過 indices 獲取實(shí)際索引后汪茧,就可讀取主鍵和值信息了。

雖然該版本按添加順序存儲元素限番,但內(nèi)部實(shí)現(xiàn)不能作為依賴條件舱污。在后續(xù)版本中,可能有其他變化弥虐。如有明確順序需求扩灯,建議使用 collections.OrderDict。

系統(tǒng)分別緩存復(fù)用 80 個 dict 和 keys霜瘪,其中包括長度為 8 的 entries 內(nèi)存珠插。對于大量小字典對象而言,直接使用颖对,無須任何內(nèi)存分配操作捻撑。回收時惜互,凡內(nèi)存被擴(kuò)張過的都會被放棄布讹。

從開發(fā)地址法(open-address)實(shí)現(xiàn)方式來看,它并不適合處理大數(shù)據(jù)训堆。輕量級方案可選用 shelve描验、dbm 等標(biāo)準(zhǔn)庫模塊,也可直接采用 SQLite坑鱼、LevelDB 等專業(yè)數(shù)據(jù)庫膘流。

構(gòu)建

創(chuàng)建字典對象可以使用大括號鍵值對方式創(chuàng)建,或調(diào)用類型構(gòu)造鲁沥。

>>> {"a": 1, "b": 2}
{'a': 1, 'b': 2}
>>> dict(a=1, b=2)
{'a': 1, 'b': 2}

初始化鍵值參數(shù)也可以用元組呼股、列表等可迭代對象的方式提供。

>>> kvs = (("a", 1), ["b", 2])
>>> dict(kvs)
{'a': 1, 'b': 2}

基于動態(tài)數(shù)據(jù)創(chuàng)建時画恰,多以 zip彭谁、map 函數(shù)或推導(dǎo)式完成。

>>> dict(zip("abc", range(3)))
{'a': 0, 'b': 1, 'c': 2}
>>> dict(map(lambda k, v: (k, v + 10), "abc", range(3)))
{'a': 10, 'b': 11, 'c': 12}
>>> {k: v + 10 for k, v in zip("abc", range(3))}
{'a': 10, 'b': 11, 'c': 12}

除了直接提供內(nèi)容外允扇,某些時候缠局,還須根據(jù)一定條件初始化字典對象则奥。比如,基于已有字典內(nèi)容擴(kuò)展狭园,或初始化零值等读处。

>>> a = {"a": 1}
>>> b = dict(a, b=1)        # 在復(fù)制 a 內(nèi)容的基礎(chǔ)上,新增鍵值對
>>> b
{'a': 1, 'b': 1}
>>> c = dict.fromkeys(b, 0)     # 僅用 b 的主鍵唱矛,內(nèi)容另設(shè)
>>> c
{'a': 0, 'b': 0}
>>> d = dict.fromkeys(("counter1", "counter2"), 0)  # 顯示提供主鍵
>>> d
{'counter1': 0, 'counter2': 0}

相比于 fromkeys 方法罚舱,推導(dǎo)式可完成更復(fù)雜的操作,比如額外的 if 過濾條件绎谦。

操作

字典不是序列類型管闷,不支持序號訪問,可以使用主鍵(鍵值)讀取燥滑、新增或刪除內(nèi)容渐北。
若主鍵(鍵值)不存在,會引發(fā) KeyError 異常铭拧,可以先用 in、not in 語句判斷是否存在該主鍵(鍵值)恃锉,或用 get 方法返回默認(rèn)值搀菩。

get 方法默認(rèn)值參數(shù)僅返回,不影響字典內(nèi)容破托。但某些時候肪跋,我們還須向字典插入默認(rèn)值,比如用字典存儲多個計(jì)數(shù)器土砂,那么在第一次取值時延遲初始化很有必要州既。在字典內(nèi)有零值內(nèi)容代表該計(jì)數(shù)曾被使用,沒有則無法記錄該行為萝映。

>>> x = {}
>>> x.setdefault("a", 0)        # 如果有 a吴叶,那么返回實(shí)際內(nèi)容,否則新增{a:0}鍵值對
0
>>> x
{'a': 0}
>>> x["a"] = 100
>>> x.setdefault("a", 0)
100

字典不支持加法序臂、乘法蚌卤、大小等運(yùn)算,但可比較內(nèi)容是否相同奥秆。

>>> {"a": 1, "b": 2} == {"a": 1, "b": 2}
True

視圖

與早期版本復(fù)制數(shù)據(jù)并返回列表不同逊彭,Python 3 默認(rèn)以視圖關(guān)聯(lián)字典內(nèi)容。如此一來构订,既能避免復(fù)制開銷侮叮,還能同步觀察字典變化。

>>> x = dict(a = 1, b = 2)
>>> ks = x.keys()       # 主鍵視圖
>>> for k in ks: print(k, x[k])     # 利用視圖迭代字典
a 1
b 2

<u>::這一段代碼不是很明白悼瘾,迭代獲取值還是從原來的字典中獲取的囊榜,為什么會叫視圖呢谷异?::</u>

字典沒有獨(dú)立的只讀版本,無論傳遞引用還是復(fù)制品锦聊,都存在弊端:

  • 直接引用有被接收方修改內(nèi)容的風(fēng)險(xiǎn)
  • 復(fù)制品僅是一次快照歹嘹,無法獲知字典的變化

視圖則不同,它能同步讀取字典內(nèi)容孔庭,卻無法修改尺上。且可選擇不同粒度的內(nèi)容進(jìn)行傳遞,如此可將接收方限定為指定模式下的觀察員圆到。

def test(d):        # 傳遞鍵值視圖(items)怎抛,只能讀取,無法修改
    for k, v in d:
        print(k, v)

視圖還支持集合操作芽淡,以彌補(bǔ)字典功能上的不足马绝。

>>> a = dict(a = 1, b = 2)
>>> b = dict(c = 3, b = 2)
>>> ka = a.keys()
>>> kb = b.keys()

>>> ka & kb     # 交集:在 a、b 中同時存在
{'b'}
>>> ka | kb     # 并集:在 a 或 b 中存在
{'b', 'a', 'c'}
>>> ka - kb     # 差集:僅在 a 中存在
{'a'}
>>> ka ^ kb     # 對稱差集:僅在 a 或僅在 b 中出現(xiàn)挣菲,相當(dāng)于“并集-交集”
{'a', 'c'}

利用視圖的集合運(yùn)算富稻,可簡化某些操作。例如白胀,只更新椭赋,不新增。

>>> a = dict(a = 1, b = 2)
>>> b = dict(b = 20, c = 3)

>>> ks = a.keys() & b.keys()        # 交集或杠,也就是 a 中必須存在的主鍵

>>> a.update({k: b[k] for k in ks}) # 利用交集結(jié)果提取待更新的內(nèi)容
>>> a
{'a': 1, 'b': 20}

拓展

在標(biāo)準(zhǔn)庫中哪怔,還有幾個擴(kuò)展類型的字典可供使用。

默認(rèn)字典(defaultdict)類似于 setdefault 方法的包裝向抢。當(dāng)主鍵不存在時认境,調(diào)用構(gòu)造參數(shù)提供的工廠函數(shù)返回默認(rèn)值。

將字典直接作為對外接口時挟鸠,無法保證用戶是否會調(diào)用 setdefault 或 get 方法叉信。這樣,默認(rèn)字典的內(nèi)置初始化行為就好于對用戶做額外要求兄猩。

>>> import collections
>>> d = collections.defaultdict(lambda : 100)
>>> d["a"]
100
>>> d["b"] += 1
>>> d
defaultdict(<function <lambda> at 0x10bfb4f28>, {'a': 100, 'b': 101})

與內(nèi)部實(shí)現(xiàn)無關(guān)茉盏,有序字典(OrderedDict)明確記錄主鍵首次插入的次序。

任何時候都要避免依賴內(nèi)部實(shí)現(xiàn)枢冤,或者說遵循“顯式優(yōu)于隱式”的規(guī)則鸠姨。

>>> d = collections.OrderedDict()
>>> d["z"] = 1
>>> d["a"] = 2
>>> d["x"] = 3
>>> for k, v in d.items(): print(k, v)
z 1
a 2
x 3

與前面所說不同,計(jì)數(shù)器(Counter)對于不存在的主鍵返回零,但不會新增,即將主鍵添加到字典中上祈。

可通過繼承并重載 __miss__ 方法新增鍵值

>>> d = collections.Counter()
>>> d["a"]
0
>>> d["b"] += 1
>>> d
Counter({'b': 1})

鏈?zhǔn)阶值洌–hainMap)以單一接口訪問多個字典內(nèi)容靠胜,其自身并不存儲數(shù)據(jù)。讀操作按參數(shù)順序依次查找各字典呻右,但修改操作(新增傲隶、更新英上、刪除)僅針對第一字典祟峦。

>>> a = dict(a = 1, b =2)
>>> a = dict(a = 1, b = 2)
>>> b = dict(b = 20, c = 30)
>>> x = collections.ChainMap(a, b)

>>> x["b"], x["c"]      # 按順序命中
(2, 30)
>>> for k, v in x.items(): print(k, v)      # 遍歷所有字典
b 2
a 1
c 30

>>> x["b"] = 999        # 更新罚斗,命中第一字典
>>> x["z"] = 888        # 新增,命中第一字典
>>> x
ChainMap({'a': 1, 'b': 999, 'z': 888}, {'b': 20, 'c': 30})

可利用鏈?zhǔn)阶值湓O(shè)計(jì)多層次的上下文(context)結(jié)構(gòu)宅楞。

合理的上下文類型针姿,須具備兩個基本特征。首先是繼承厌衙,所有設(shè)置可被調(diào)用鏈的后續(xù)函數(shù)讀染嘁;其次是修改僅針對當(dāng)前和后續(xù)邏輯婶希,不應(yīng)向無關(guān)的父級傳遞榕暇。如此,鏈?zhǔn)阶值洳檎掖涡虮旧砭褪抢^承的體現(xiàn)喻杈,而修改操作被限制在當(dāng)前第一字典中中彤枢,自然也不會影響父級字典的同名主鍵設(shè)置。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奕塑,一起剝皮案震驚了整個濱河市堂污,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌龄砰,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讨衣,死亡現(xiàn)場離奇詭異换棚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)反镇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門固蚤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人歹茶,你說我怎么就攤上這事夕玩。” “怎么了惊豺?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵燎孟,是天一觀的道長。 經(jīng)常有香客問我尸昧,道長揩页,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任烹俗,我火速辦了婚禮爆侣,結(jié)果婚禮上萍程,老公的妹妹穿的比我還像新娘。我一直安慰自己兔仰,他們只是感情好茫负,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著乎赴,像睡著了一般忍法。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上无虚,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天缔赠,我揣著相機(jī)與錄音,去河邊找鬼友题。 笑死嗤堰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的度宦。 我是一名探鬼主播踢匣,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼戈抄!你這毒婦竟也來了离唬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤划鸽,失蹤者是張志新(化名)和其女友劉穎输莺,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裸诽,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嫂用,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了丈冬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嘱函。...
    茶點(diǎn)故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖埂蕊,靈堂內(nèi)的尸體忽然破棺而出往弓,到底是詐尸還是另有隱情,我是刑警寧澤蓄氧,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布函似,位于F島的核電站,受9級特大地震影響匀们,放射性物質(zhì)發(fā)生泄漏缴淋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望重抖。 院中可真熱鬧露氮,春花似錦、人聲如沸钟沛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恨统。三九已至叁扫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間畜埋,已是汗流浹背莫绣。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悠鞍,地道東北人对室。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像咖祭,于是被迫代替她去往敵國和親掩宜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容