第三章–字典與集合
本章內容的大綱如下:
• 常見的字典方法
• 如何處理查找不到的鍵
• 標準庫中 dict 類型的變種
• set 和 frozenset 類型
• 散列表的工作原理
• 散列表帶來的潛在影響(什麼樣的數據類型可作爲鍵、不可預知的順序,等等)
3.1泛映射類型
collections.abc模塊中有Mapping和MutableMapping兩個抽象基類,作用是給dict或其他類似的映射類型定義接口,還有可以用與isinstance()判斷某個數據是否是映射類型
標準庫中所有映射類型都是利用dict實現了,限制是只有hashable的數據類型纔可以作爲映射的key
關於hashbale的一些解釋
- 原子不可變數據類型 str、bytes、數值類型都是hashable的
- frozenset是hashable的,因爲根據其定義frozenset裏只能容納hashable類型
- 元祖只有它包含的所有元素都是hashable的,它纔是hashable的
tt=(1,2,(30,40))
print(hash(tt)) # sucess
tl=(1,2,[30,40])
print(hash(tl)) # TypeError: unhashable type: '
- 用戶自定義類型hashable
1.用到__hash__的情況有調用hash的時候跟在set、frozenset、dict等哈希集合中
官方文檔 https://docs.python.org/3/reference/datamodel.html#object.hash
非常建議去讀一下官方文檔
Called by built-in function hash() and for operations on members of hashed collections including set, frozenset, and dict.
- 關鍵是__hash__跟__eq__的實現
- hash跟eq都不實現的話會用object的hash跟eq函數,object的hash跟eq都是返回對象的內存地址,這時a1==a2 a1 is a2 hash(a1)==hash(a2)的返回值都是一樣的,如果a1 a2都是指向同一個對象的話,結果都是True ,如果a1 a2不是指向同一對象,結果都爲False
class ClassA():
def __init__(self):
self.name='jason'
a1=ClassA()
a2=ClassA()
print(a1==a2, a1 is a2, hash(a1)==hash(a2)) # False False False
a2=a1
print(a1==a1, a1 is a2, hash(a1)==hash(a2)) # True True True
- 自定義hash,不實現eq,這樣會用到object的eq函數,作爲dict的key值時會調用hash跟eq函數,如果兩個key的hash相同,還會調用key的eq函數,如果eq結果爲False,那麼會把兩個key識別爲不同,儘管他們的hash相同,如下面的代碼,dic最終有兩個key
class ClassA():
def __init__(self):
self.name='jason'
def __hash__(self):
print('ClassA Hash method')
return hash(self.name)
a1=ClassA()
a2=ClassA()
print(a2.__hash__)
dic={}
dic[a1]=1
dic[a2]=2
print(dic)
# <bound method ClassA.__hash__ of <__main__.ClassA object at 0x000001283708D160>>
# ClassA Hash method
# ClassA Hash method
# {<__main__.ClassA object at 0x000001283708D438>: 1, <__main__.ClassA object at 0x000001283708D160>: 2}
- 自定義eq不定義hash,只定義了eq的話hash方法會變爲None,不可調用,此時不能作爲dict的key
class ClassA():
def __init__(self):
self.name='jason'
def __eq__(self, other):
print('ClassA Eq method')
return self.name==other.name
a1=ClassA()
a2=ClassA()
print(a2.__hash__) # None
dic={}
dic[a1]=1 # TypeError: unhashable type: 'ClassA'
dic[a2]=2
print(dic)
- 如果同時定義了hash跟eq,作爲dict的key時都會調用,只有兩個變量hash值相同,eq返回True時才能判定爲同一個key,下面兩處代碼分別展示了兩個變量hash值相同,但eq返回True跟False的情況
class ClassA():
def __init__(self):
self.name='jason'
def __hash__(self):
print('ClassA Hash method')
return hash(self.name)
def __eq__(self, other):
print('ClassA Eq method')
return self.name==other.name
a1=ClassA()
a2=ClassA()
print(a2.__hash__)
dic={}
dic[a1]=1
dic[a2]=2
print(dic)
# <bound method ClassA.__hash__ of <__main__.ClassA object at 0x000001EEFFE1D470>>
# ClassA Hash method
# ClassA Hash method
# ClassA Eq method
# {<__main__.ClassA object at 0x000001EEFFE1D160>: 2}
class ClassA():
def __init__(self):
self.name='jason'
def __hash__(self):
print('ClassA Hash method')
return hash(self.name)
def __eq__(self, other):
print('ClassA Eq method')
# return self.name==other.name
return self.name=='mike'
a1=ClassA()
a2=ClassA()
print(a2.__hash__)
dic={}
dic[a1]=1
dic[a2]=2
print(dic)
# <bound method ClassA.__hash__ of <__main__.ClassA object at 0x00000201586FD4E0>>
# ClassA Hash method
# ClassA Hash method
# ClassA Eq method
# {<__main__.ClassA object at 0x00000201586FD470>: 1, <__main__.ClassA object at 0x00000201586FD4E0>: 2}
- 參考:
https://blog.csdn.net/sinat_38068807/article/details/86519944
https://blog.csdn.net/lnotime/article/details/81194962
3.2字典推導
跟列表推導式類似
實現key跟value的互換:dic={value,key for key,value in other_dic.items()}
3.3 常見的映射方法
展示了dict、collections.defaultdict、collections.OrdereDict的方法列表
3.4 映射的彈性key查詢
有時候爲了方便起見,就算某個鍵在映射裏不存在,我們也希望在通過這個鍵讀取值的
時候能得到一個默認值。有兩個途徑能幫我們達到這個目的,一個是通過 defaultdict 這
個類型而不是普通的 dict,另一個是給自己定義一個 dict 的子類,然後在子類中實現
__missing__ 方法。
- defaultdict
舉例 dd=collections.defaultdict(list)
執行dd[key]時,如果key在dd.keys()裏則返回dd[key],如果key不在的話就會調用list()得到默認值賦值給dd[key],並返回列表的引用
- __missing__方法
__missing__方法只會被__getitem__調用,跟get或者__contains__無關,這個跟defaultdict的default_factory一個道理,當d[key]找不到key對應的值後就會調用missing方法
3.5 字典的變種
- collections.OrderedDict
添加key時會保持順序,key的迭代順序是一致的,popitem默認刪除字典最後一個元素,如果是popitem(last=False)的話會刪除第一個元素 - collections.ChainMap
通過鏈式的形式存儲多個映射對象,在查找key時對所有映射對象進行查詢
查詢會查詢所有對象,修改只會修改第一個映射對象裏的key
from collections import ChainMap
dicA={'a':1,'b':2}
dicB={'b':3,'c':4}
cm=ChainMap(dicA,dicB)
print(cm)# ChainMap({'a': 1, 'b': 2}, {'b': 3, 'c': 4})
print(cm['b']) # 2
cm.pop('b')
print(cm) # ChainMap({'a': 1}, {'b': 3, 'c': 4})
print(dicA) # {'a': 1}
print(cm['b']) # 3
cm.pop('c') # KeyError: "Key not found in the first mapping: 'c'"
- collections.Counter
給key準備一個整數計數器,每次更新一個key時增加家屬器
from collections import Counter
ct=Counter('abcdefg')
print(ct)
ct.update('aaaabbddd')
print(ct)
print(ct.most_common(2))
# Counter({'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1, 'f': 1, 'g': 1})
# Counter({'a': 5, 'd': 4, 'b': 3, 'c': 1, 'e': 1, 'f': 1, 'g': 1})
# [('a', 5), ('d', 4)]
- collections.UserDict
把標準dict用純Python又實現了一遍,3.6詳細介紹
3.6子類化UserDict
UserDict並不是dict的子類,更像是對dict的一個封裝,UserDict有一個data屬性,是dict實例,實際上data是數據最終存儲的地方,我們構建映射類繼承UserDict而不是dict,可以避免dict隱式調用自身的某些方法
3.7不可變映射類型
不可變映射類型意思是指用戶能讀取映射的值,但不能添加新key或修改value值
from types import MappingProxyType
d={'a':1}
mp=MappingProxyType(d)
print(mp)# {'a': 1}
print(mp['a']) # 1
# mp['b']=2 # TypeError: 'mappingproxy' object does not support item assignment
# mp['a']=3 # TypeError: 'mappingproxy' object does not support item assignment
d['a']=4
print(mp) # {'a': 4}
3.8集合論
集合中的元素必須是hashable的,set類型本身不是hashable的,但是frozenset可以,因此可以創建一個包含不同frozenset的set
集合的運算 集合a、b a|b返回合集 a&b返回交集,a-b返回差集
集合推導,跟字典推導類似
s={key for key in my_dict.keys()}
集合的函數列舉
dict和set的背後原理
dict和set背後都是用散列表
給予散列表實現的dict和set有以下特點
- 集合裏的元素必須是可散列的。
- 集合很消耗內存。
- 可以很高效地判斷元素是否存在於某個集合。
- 元素的次序取決於被添加到集合裏的次序。
- 往集合裏添加元素,可能會改變集合裏已有元素的次序。
注意,不要在遍歷dict和set的同時對他們的值進行修改,這可能導致某些值不被遍歷到
dict的keys items values方法返回的都是字典視圖,而不是列表