python泛對映型別
阿新 • • 發佈:2022-03-11
目錄
泛對映型別定義
- 泛對映型別即鍵值對型別,最常見的當然就是字典,鍵值對中的鍵必須是可雜湊的,可雜湊物件要滿足以下要求:
- 在此物件的生命週期中,雜湊值不變
- 需要實現特殊方法__hash__
- 要有__qe__方法
- 若兩個可雜湊物件相等,則其雜湊值一定相等
- 常見可雜湊物件:
- 原子不可變資料型別,如str、bytes、陣列型別
- frozenset
- 若元組中元素均可雜湊,則元組物件可雜湊
- 自定義物件可雜湊,雜湊值即id()返回值,即記憶體地址
泛對映型別抽象基類
- collections.abc模組中有Mapping和MutableMapping兩個抽象基類,其作用是為dict和其它泛對映型別提供抽象介面。
- 可以用isinstance函式判斷某個物件是不是泛對映型別,isinstance會將子類和父類判定為同一種物件。抽象基類當然是所有泛對映型別的父類。
import collections my_dict = {} print(isinstance(my_dict, collections.Mapping)) # 輸出True
字典構建
- 字典構建有很多種方式,可以用{}也可以用dict()。需要注意的是字典是無序的,即構建字典時鍵值對的順序不重要。
字典推導式
- 字典推導式和列表推導式,生成器表示式格式類似,只不過for迴圈中的可迭代物件的元素是鍵值對,即兩個元素的元組。
name_id = [('xiaoming', 1), ('xiaohong', 2), ('xiaogang', 3)] d1 = {name: id for name, id in name_id} d2 = {id: name for name, id in name_id}
處理找不到的鍵
get()方法
- 當我們用d[k]來獲取字典d的鍵k對應的值的時候,會發生以下幾個步驟:
- 首先查詢字典d有沒有鍵k
- 如果有,根據k的雜湊值找到對應的值
- 如果沒有,丟擲異常
- 當鍵k不存在時,異常處理比較麻煩,因此可以用d.get(k, default)來代替d[k],此時鍵k若不存在則不會丟擲異常,而是返回default。
- 用get()方法存在一個問題,如果我們不是為了查詢鍵k的值,而是為了給鍵k賦值,即d.get(k, default) = num,此時若鍵k不存在,則num會被賦值給default,這顯然是不符合邏輯的。對於這種情況,要使用setdefault方法。
setdefault方法
- setdefault方法定義
d.setdefault(k, [default])
- 若字典中有鍵k,則將它對應的值置為default
- 若字典中無鍵k,則把default賦值給鍵k,即d[k] = default,然後返回default
- setdefault方法用於給字典某個鍵重新賦值或重新插入一個鍵值對
特殊方法__missing__
__missing__方法應用場景
- 對映型別通過__getitem__方法找不到鍵的時候,會自動呼叫__missing__方法。比如d[k]找不到鍵k。
- 通過get或__contains__方法(in操作符底層)找不到鍵時不會呼叫__missing__。
__missing__使用例子
- 下邊例子中,__missing__方法裡先判斷鍵key是不是str,是為了防止無限遞迴。否則就會發生以下步驟:
- __getitem__找不到鍵,呼叫__missing__
- __mising__返回self[str(key)],由於key本身就是字串,str(key) == key,因此又會呼叫__getitem__
- 然後__getitem__找不到,繼續呼叫__missing__
- 該例子也說明了__missing__的用法,有時候鍵是字串,而我們沒有輸入字串,於是找不到(比如1和'1'),這個時候自動呼叫__missing__將鍵轉成字串重新查詢。
class StrKeyDict0(dict): # StrKeyDict0繼承自dict def __missing__(self, key): if isinstance(key, str): raise KeyError(key) # __missing__是用來處理找不到的鍵的,如果找不到的鍵本身就是字串,那麼會觸發KeyError異常 return self[str(key)] # 如果找不到的鍵本身不是字串,那麼就轉成字串再查詢一次 def get(self, key, default=None): try: return self[key] # get方法這裡實際上把查詢工作委託給__getitem__了,因為這裡用了【】索引 except KeyError: # 如果丟擲了KeyError,那麼說明__missing__也失敗了,就直接返回default了 return default def __contains__(self, key): # 先按照傳入的鍵來查詢,如果找不到就轉換成字串再找一次 return key in self.keys() or str(key) in self.keys()
k in my_dict.keys()
- 該操作查詢鍵是否存在在python3中是很快的,即便對映型別物件很大也沒關係,因為dict.keys()返回的是一個檢視,在視圖裡查詢元素速度很快。
字典變種
collections.defaultdict
- defaultdict實現了__missing__方法,用於處理找不到鍵的問題,其用法格式為:
index = collections.defaultdict(default) index[key]
- defaultdict在物件構建的時候傳入的引數default必須是可呼叫物件,該可呼叫物件會賦值給defaultdict中的一個物件屬性default_factory
- 如果index(key)找不到鍵key,則會自動呼叫default物件,然後經default返回的值賦值給鍵key並將鍵值對加入字典index,並將default返回值返回。
collections.OrderedDict
- 普通字典是無序的,但是該有序字典不是說按照鍵的字典序排序,而是說保持插入順序。
collections.ChainMap
collections.Counter
collections.UserDict
- UserDict是專門用於自定義對映型別的基類
不可變對映型別
- 標準庫中對映型別均可變,types模組中的MappingProxyType,其初始化引數接受一個對映物件,返回一個只讀的對映檢視,該檢視雖不能更改,但是原始初始化引數的改變可以反應到該檢視上。
集合論
set&frozenset
- python中集合類主要有set和frozenset兩種。set物件是不可雜湊的,frozenset物件是可雜湊的。
- 集合的本質是許多唯一物件的集合,所謂唯一物件指的是集合中的元素都是不同的,因此集合可以用於去重。
- 集合的背後是散列表,因此查詢速度極快。
- 集合中的元素必須可雜湊,因此如果要定義巢狀集合物件,則裡層元素不能是set物件,可以是frozenset物件。
- 集合實現了交併差(& | -)等操作。
集合字面量
- set定義集合兩種方式:
- 直接用{}定義,s = {1, 2, 3}
- 用set定義,s = set([1, 2, 3])
- 直接定義速度更快,因為用set()定義的時候,python會先去查詢set的建構函式,然後新建一個列表,最後把列表傳入到建構函式裡,但是直接用 { }的話python會利用一個專門的叫做BUILD_SET的位元組碼來建立集合。
- 空集合定義只能用 s = set()而不能是s = {},因為後者定義的是字典。
- frozenset定義集合只能用建構函式,f = frozenset(range(10))。
集合推導式
- s = {i for i in range(10)}
dict和set的背後機制
dict與散列表
- 散列表是一個稀疏陣列,其中單元叫表元,在dict中,每個鍵值對佔用一個表元,即表元分為兩部分,一個是對鍵的引用,一個是對值的引用。
- python保證散列表中三分之一表元是空的,快到這個閾值時散列表會被複制到一個更大的空間中。
- 散列表中每個表元大小相同,因此可根據鍵的散列表確定偏移量,通過偏移量定位到某個表元。因此dict中鍵必須可雜湊。
- python中計算某個元素雜湊值用hash()實現。
- 字典在記憶體上開銷巨大,因為散列表是稀疏的,是典型的空間換時間,鍵查詢很快,常數時間複雜度訪問。
- 鍵的次序取決於新增順序,雖然字典無序,但是鍵在字典中的順序是不同的,如果兩個鍵發生雜湊衝突,則後新增的鍵會被安排到另一個位置。
- 新增新鍵會改變已有鍵的順序。因為無論合適新增,python都有可能為字典擴容,新建一個更大的散列表,過程中可能發生新的雜湊衝突,導致散列表中鍵的次序變化。因此,不要在迴圈字典的同時修改,因此可能因為順序變化而導致跳過一些鍵。
set/frozenset與散列表
- set和frozenset背後也是散列表,表元中只儲存鍵的引用,沒有值的引用。
- 集合中元素必須可雜湊。
- 集合很佔記憶體。
- 可以快速判斷元素是否在某個集合。
- 元素次序取決於新增到集合的次序。
- 新增元素可能改變集合裡已有元素的次序。