流暢的python讀書筆記-第三章Python 字典與集合

字典和集合

標準庫裏的全部映射類型都是利用 dict 來實現的
只有可散列的數據類型才能用做這些映射裏的鍵(值不用)python

可散列

  1. 一個對象是可散列的
  2. 它的散列值是不變的
  3. 對象須要實現 __hash__() 方法
  4. 可散列對象還要有 __qe__() 方法

字典推導

DIAL_CODES = [(86, 'China'), (91, 'India'), (1, 'United States'), (62, 'Indonesia') ]

country_code = {country: code        for code, country in DIAL_CODES     }
結果
{'China': 86, 'India': 91, 'United States': 1, 'Indonesia': 62}

常見的映射方法 page137

用setdefault處理找不到的鍵

##找對應的key,沒有的話返回默認值
my_dict = {"name":"longe","age":8}
my_dict.setdefault("namerrr","default")

print(my_dict)

用 setdefault 只須要一次就能夠完成整個操做。測試

defaultdict找不到鍵返回某種默認值

  • 在實例化一個 defaultdict 的時候
  • 這個可調用對象會在 getitem 碰到找不到的鍵的時候被調用,
  • getitem 返回某種默認值。

實現方式

  • defaultdict 裏的 default_factory 只會在__getitem__ 裏被調用
好比,dd 是個 defaultdict,k 是個找不到的鍵,
  • dd[k] 這個表達式會調用 default_factory 創造某個默認值,
  • dd.get(k) 則會返回 None。
原理
全部這一切背後的功臣實際上是特殊方法 __missing__。
它會在defaultdict 遇到找不到的鍵的時候調用 default_factory

__missing__這個方法

自定義一個映射類型,更合適的策略實際上是繼承collections.UserDict 類spa

只是爲了演示 missing 是如何被dict.__getitem__ 調用的。code

class StrKeyDict0(dict):
    def __missing__(self, key):

        if isinstance(key, str):
            raise KeyError(key)
            return self[str(key)]

    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

    def __contains__(self, key):
        return key in self.keys() or str(key) in self.keys()
isinstance(key, str) 測試在上面的__missing__ 中是必需的
可是若是 str(k) 不是一個存在的鍵,代碼就會陷入無限遞歸。

這是由於 missing 的最後一行中的 self[str(key)] 會調用 __getitem__,
而這個 str(key) 又不存在,因而 __missing__又會被調用。對象

精簡版本blog

import collections


class StrKeyDict(collections.UserDict):
    def __missing__(self, key):
        if isinstance(key, str):
            raise KeyError(key)
        return self[str(key)]

    def __contains__(self, key):
        return str(key) in self.data

    def __setitem__(self, key, item):
        self.data[str(key)] = item
setitem 會把全部的鍵都轉換成字符串。因爲把具體的實現委
託給了 self.data 屬性,這個方法寫起來也不難

字典的變種

collections.OrderedDict

這個類型在添加鍵的時候會保持順序,所以鍵的迭代次序老是一致
的。繼承

collections.ChainMap

該類型能夠容納數個不一樣的映射對象,而後在進行鍵查找操做的時
候,這些對象會被看成一個總體被逐個查找,直到鍵被找到爲止。遞歸

collections.Counter

這個映射類型會給鍵準備一個整數計數器。每次更新一個鍵的時候
都會增長這個計數器。ip

colllections.UserDict

這個類其實就是把標準 dict 用純 Python 又實現了一遍。
跟 OrderedDict、ChainMap 和 Counter 這些開箱即用的類型不
同,UserDict 是讓用戶繼承寫子類的。下面就來試試。內存

集合論

集合推導

from unicodedata import name

aa = {chr(i) for i in range(32, 256) if 'SIGN' in name(chr(i), '')}
print(aa)

集合的數學運算 page161

字典空間

  • 由於 Python 會設法保證大概還有三分之一的表元是空的,因此在快要達

到這個閾值的時候,原有的散列表會被複制到一個更大的空間裏面。

散列表原理

  1. 爲了獲取 my_dict[search_key] 背後的值
  2. Python 首先會調用hash(search_key) 來計算 search_key 的散列值,
  3. 把這個值最低的幾位數字看成偏移量
  4. 在散列表裏查找表元(具體取幾位,得看當前散列表的大小
  5. 若找到的表元是空的,則拋出 KeyError 異常。
  6. 若不是空的,則表元裏會有一對 found_key:found_value。
  7. 這時候 Python 會檢驗 search_key == found_key 是否爲真,如 果它們相等的話,就會返回found_value。
  8. 若是 search_key 和 found_key 不匹配的話,這種狀況稱爲散列 衝突。

原理圖

clipboard.png

添加新元素和更新現有鍵值

添加新元素和更新現有鍵值的操做幾乎跟上面同樣。
只不過對於前者,在發現空表元的時候會放入一個新元素;
對於後者,在找到相對應的表元后,原表裏的值對象會被替換成新值。

優劣

字典浪費存儲空間(不過沒有幾百萬對象,內存好幾個G不用考慮)
dict 的實現是典型的空間換時間:字典類型有着巨大的內存開銷

鍵的次序取決於添加順序

當往 dict 裏添加新鍵而又發生散列衝突的時候,新鍵可能會被安
排存放到另外一個位置。

注意:

  • 不管什麼時候往字典裏添加新的鍵,Python 解釋器均可能作出爲字典擴

容的決定。

  • 擴容致使的結果就是要新建一個更大的散列表,並把字典裏已有的元素添加到新表裏。
  • 這個過程當中可能會發生新的散列衝突,致使新散列表中鍵的次序變化。
  • 要注意的是,上面提到的這些變化是否會發生以及如何發生,都依賴於字典背後的具體實現,
  • 所以你不能很自信地說本身知道背後發生了什麼。
  • 若是你在迭代一個字典的全部鍵的過程當中同時對字典進行修改,那麼這個循環頗有可

能會跳過一些鍵——甚至是跳過那些字典中已經有的鍵。

更新字典的主要使用姿式

  • 由此可知,不要對字典同時進行迭代和修改。
  • 若是想掃描並修改一個字典,最好分紅兩步來進行:
  • 首先對字典迭代,以得出須要添加的內容,把這些內容放在一個新字典裏;
  • 迭代結束以後再對原有字典進行更新。

小總結:

  • 標準庫裏的全部映射類型都是利用 dict 來實現
  • 只有可散列的數據類型才能用做這些映射裏的鍵(值不用)
  • 字典推導
  • 用setdefault處理找不到的鍵
  • defaultdict找不到鍵返回某種默認值
  • 底層是 getitem 與__miss__調用實現的
  • 字典插入更新原理!!!

其餘

  • 大多數映射類型都提供了兩個很強大的方法:setdefault 和

update。

  • setdefault 方法能夠用來更新字典裏存放的可變值(好比列

表),從而避免了重複的鍵搜索。

  • update 方法則讓批量更新成爲可能,它能夠用來插入新值或者更新已有鍵值對,它的參數能夠是包含(key, value) 這種鍵值對的可迭代對象,或者關鍵字參數。
相關文章
相關標籤/搜索