1. 程式人生 > 其它 >python泛對映型別

python泛對映型別

目錄

泛對映型別定義

  • 泛對映型別即鍵值對型別,最常見的當然就是字典,鍵值對中的鍵必須是可雜湊的,可雜湊物件要滿足以下要求:
    • 在此物件的生命週期中,雜湊值不變
    • 需要實現特殊方法__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背後也是散列表,表元中只儲存鍵的引用,沒有值的引用。
  • 集合中元素必須可雜湊。
  • 集合很佔記憶體。
  • 可以快速判斷元素是否在某個集合。
  • 元素次序取決於新增到集合的次序。
  • 新增元素可能改變集合裡已有元素的次序。