Python中的字典跟集合整理筆記
泛對映型別
對映型別:不僅僅是dict,標準庫裡的所有對映型別都是利用dict來實現的,因此它們有個共同的限制,即只有可雜湊的資料型別才能用做這些對映的鍵。(只有鍵有這個需求,值並不需要必須是可雜湊的資料型別。)
什麼是可雜湊的資料型別?
可雜湊的物件在它的生命週期中,雜湊值是不變的,需要實現hash ()方法。另外雜湊物件還要有eq ()方法,這樣才能跟其他鍵作比較。 - 原子不可變資料型別都是可雜湊的(str,bytes和數值型別,frozenset) - dict,list是不可雜湊的
用setdefault處理找不到的鍵
當字典d[k]找不到值會丟擲異常,通常我們使用d.get(k,default)來代替d[k],給找不到的鍵預設一個返回值。 但是要更新某個鍵對應的值的時候,不管是用getitem 還是get都不太自然的,效率很低。
這樣使用 setdefault
my_dict.setdefault(key,[]).append(new_value)
跟這樣寫使用預設的dict
if key not in my_dict:
my_dict[key]=[]
my_dict[key].append(new_value)
兩者的效果是一樣的,只不過後者至少要進行兩次查詢——如果鍵不存在的話,就是三次,使用setdefault只需要一次就可以完成整個操作。
對映的彈性查詢
所謂的彈性查詢就是,我找的鍵不在對映裡面存在的時候,也能返回一個預設值比如
d.get(key,default)
python有兩個途徑達到整個目的, - 一個是通過defaultdict這個型別而不是普通的dict - 另一個是給自己頂一個dict的子類,然後在子類中實現missing 方法。
defaultdict:處理找不到的鍵的一個選擇
dd = defaultdict(list)
print(dd['new-key']) # []
"""
呼叫list()來建立一個列表。
把這個新列表作為值,'new-key'作為它的鍵,放到dd中。
返回這個列表的引用。
"""
print(dd) #defaultdict(<class 'list'>, {'dddd': []})
注意如果在建立defaultdict的時候沒有指定default_factory,查詢不存在鍵會觸發KeyError
特殊方法missing
所有的對映型別找不到鍵的時候,都會使用到missing 方法。 雖然基類dict並沒有定義這個方法,但是dict知道有這麼個東西的存在。 也就是說,如果有一個類繼承了dict,然後這個繼承類提供了missing 方法, 那麼在getitem 碰到找不到鍵的時候,python會自動呼叫它,而不是丟擲一個KeyError異常。missing 方法只會被getitem 呼叫
字典的變種
collections.OrderedDict:這個型別在新增鍵的時候會保持順序,因此鍵的迭代次序總是一致的。
collections.ChainMap:該型別可以容納數個不同的物件,然後在進行鍵查詢操作的時候,這些物件會被當做一個整體逐個被查詢。
import collections
初始化字典
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
初始化ChainMap
chain = collections.ChainMap(dict1, dict2)
使用maps輸出chainMap
print(chain.maps)# [{'b': 2, 'a': 1}, {'b': 3, 'c': 4}]
輸出key
print(list(chain.keys()))# ['b', 'c', 'a']
輸出值
print(list(chain.values()))# [2, 4, 1]
訪問
print(chain['b'])# 2
print(chain.get('b'))# 2
使用new_child新增新字典
dict3 = {'f': 5}
new_chain = chain.new_child(dict3)
print(new_chain.maps)# [{'f': 5}, {'b': 2, 'a': 1}, {'b': 3, 'c': 4}]
reversed(new_chain.maps)
print(new_chain.maps)
collections.Counter:這個對映型別會給鍵準備一個整數計數器。每次更新一個鍵的時候都會增加這個計數器。
collections.UserDict:把標準的dict用純python又實現了一遍。
不可變的對映型別
標準庫裡所有的對映型別都是可變的,如果遇到不能讓使用者錯誤的修改某個對映。 使用types.MappingProxyType,如果給這個類一個對映,它會返回一個只讀的對映檢視(動態的)。
集合
相對dict,set這個概念在python算是比較年輕的,有set 跟 frozenset 集合的本質是許多唯一物件的聚集,所以集合中的元素必須都是可雜湊的,set型別本身是不可雜湊的,但是frozenset時可雜湊的。 集合可以進行中綴運算子。
dict和set的背後
python裡的dict和set的效率有多高?
為什麼它們是無序的?
為什麼並不是所有的python物件都可以當做dict的鍵或者是set的元素?
為什麼dict的鍵和set元素的順序是根據他們被新增的次序而定的,以及為什麼在對映物件的生命週期中,這個順序是一成不變的?
為什麼不應該在迭代迴圈dict或者是set的同時往裡新增元素?
字典中的散列表
散列表其實是一個稀疏陣列(總是有空白元素的陣列成為稀疏陣列) 散列表裡的單元通常叫做表元(bucket),在dict的散列表中每個鍵值對都佔用一個表元,每個表元分都有兩個部分,一個是對鍵的引用,一個是對值的引用。 如果把物件放到散列表,那麼首先要計算這個元素鍵的雜湊值,python中可以用hash()方法來做這個事情。
雜湊值和相等性
如果1==1.0為真,那麼`hash(1)==hash(1.0)也必須為真
散列表演算法
為了獲取my_dict[search_key]背後的值,Python首先呼叫hash(search_key)來計算search_key的雜湊值,把這個值最低的幾位數字當做偏移量,在散列表裡查詢表元(具體取幾位,得看當前散列表的大小)。 若找到的表元為空的,則丟擲KeyError異常。若不為空的,則表元裡會有一對found_key:found_value。 這時候python會檢驗search_key == found_key是否為真,如果它們相等,就回返回found_value。 如果search_key和found_key不匹配的話,這種情況稱為雜湊衝突。 發生原因是散列表所做的其實是把隨機的元素對映到只有幾位的數字上,而散列表本身的索引又只依賴於這個數字的一部分。 為了解決雜湊衝突,演算法會在雜湊值中另外再取幾位,然後用特殊方法處理一下,把新的到的資料在當做索引來尋找表元。
問題:如果定位一個表元?[^2]
dict的實現及其導致的結果
散列表帶給dict的優勢和限制。
鍵必須是可雜湊的
一個可雜湊的物件必須滿足以下的需求: - 支援hash()函式,並且通過hash ()方法所得到的雜湊值是不變的。 - 支援通過eq ()方法來檢測相等性。 - 若 a == b為真,則hash(a) == hash(b)也為真。
使用者自定義的物件預設是可雜湊的,它們的雜湊值有id()來獲取。
字典在記憶體上面開銷巨大
通常不需要優化,如果資料量巨大考慮使用tuple()來替代dict()
特殊方法slots
鍵查詢很快
dict的實現是典型的空間換時間
鍵的次序取決於新增順序
當往dict中新增新建而又發生雜湊衝突的時候,新建可能會被安排存放在另個一個位置。
dict([key1,value1],[key2,value2]) == dict([key2,value2],[key1,value1]) # true
雖然鍵的次序是亂的,但是被視作相等的。這就是為什麼說字典是無序的原因。
往字典中新增新建可能會改變已有鍵的順序
無論何時往字典新增新的鍵,Python的直譯器都可能做出為字典擴容的決定。 擴容導致的結果是需要一個更大散列表,並把字典裡已有的元素新增到新表裡,這個過程就可能出現雜湊衝突,導致新的散列表中的鍵的次序變化。
所以不要對字典同時迭代和修改。 python3中的.keys() .items() 和.values()方法返回都是字典的檢視,這些方法返回的值更像是集合。
set的實現以及導致的結果可以參照沒有值dict,python學習交流群 984632579 除了分享技術文章之外還有很多福利,加群領取學習資料可以領取包括不限於Python實戰演練、PDF電子文件、面試集錦、學習資料等。