python 字典d[k]中key不存在的解決方案
有時(shí)候?yàn)榱朔奖闫鹨?,就算某個(gè)鍵在映射里不存在,我們也希望在通過這個(gè)鍵讀取值的時(shí)候能得到一個(gè)默認(rèn)值。有兩個(gè)途徑能幫我們達(dá)到這個(gè)目的,一個(gè)是通過 defaultdict 這個(gè)類型而不是普通的 dict,另一個(gè)是給自己定義一個(gè) dict 的子類,然后在子類中實(shí)現(xiàn)__missing__ 方法。下面將介紹這兩種方法。
defaultdict:處理找不到的鍵的一個(gè)選擇
下面示例在 collections.defaultdict 的幫助下優(yōu)雅地解決了d[k]里的問題。在用戶創(chuàng)建 defaultdict 對(duì)象的時(shí)候,就需要給它配置一個(gè)為找不到的鍵創(chuàng)造默認(rèn)值的方法。
# -*- coding: utf-8 -*-
from collections import defaultdict
index = defaultdict(list) # 把 list 構(gòu)造方法作為 default_factory 來創(chuàng)建一個(gè) defaultdict
print(index)
# defaultdict(<class 'list'>, {})
word = 'name'
# 如果 index 并沒有 word 的記錄,那么 default_factory 會(huì)被調(diào)用,為查詢不到的鍵創(chuàng)造一個(gè)值。
# 這個(gè)值在這里是一個(gè)空的列表,然后這個(gè)空列表被賦值給 index[word],繼而
# 被當(dāng)作返回值返回,因此 .append(location) 操作總能成功。
index[word].append('mmmm')
print(index)
# defaultdict(<class 'list'>, {'name': ['mmmm']})
具體而言,在實(shí)例化一個(gè) defaultdict 的時(shí)候,需要給構(gòu)造方法提供一個(gè)可調(diào)用對(duì)象,這個(gè)可調(diào)用對(duì)象會(huì)在 __getitem__ 碰到找不到的鍵的時(shí)候被調(diào)用,讓 __getitem__ 返回某種默認(rèn)值。
比如,我們新建了這樣一個(gè)字典:dd = defaultdict(list),如果鍵 new-key在 dd 中還不存在的話,表達(dá)式 dd['new-key'] 會(huì)按照以下的步驟來行事。
- 調(diào)用
list()來建立一個(gè)新列表 - 把這個(gè)新列表作為值,‘
new-key’ 作為它的鍵,放到dd中 - 返回這個(gè)列表的
引用
而這個(gè)用來生成默認(rèn)值的可調(diào)用對(duì)象存放在名為 default_factory 的實(shí)例屬性里。
注意:
如果在創(chuàng)建 defaultdict 的時(shí)候沒有指定 default_factory,查詢不存在的鍵會(huì)觸發(fā)KeyError。
defaultdict 里的 default_factory 只會(huì)在 __getitem__ 里被調(diào)用,在其他的方法里完全不會(huì)發(fā)揮作用。比如,dd 是個(gè) defaultdict,k 是個(gè)找不到的鍵,dd[k] 這個(gè)表達(dá)式會(huì)調(diào)用 default_factory 創(chuàng)造某個(gè)默認(rèn)值,而 dd.get(k) 則會(huì)返回 None。
所有這一切背后的功臣其實(shí)是特殊方法 __missing__。它會(huì)在 defaultdict 遇到找不到的鍵的時(shí)候調(diào)用 default_factory,而實(shí)際上這個(gè)特性是所有映射類型都可以選擇去支持的。
特殊方法__missing__
所有的映射類型在處理找不到的鍵的時(shí)候,都會(huì)牽扯到 __missing__ 方法。這也是這個(gè)方法稱作“missing”的原因。雖然基類 dict 并沒有定義這個(gè)方法,但是 dict 是知道有這么個(gè)東西存在的。也就是說,如果有一個(gè)類繼承了 dict,然后這個(gè)繼承類提供了 __missing__ 方法,那么在 __getitem__ 碰到找不到的鍵的時(shí)候,Python 就會(huì)自動(dòng)調(diào)用它,而不是拋出一個(gè)KeyError 異常。
注意:__missing__ 方法只會(huì)被 __getitem__ 調(diào)用(比如在表達(dá)式 d[k] 中)。提供__missing__ 方法對(duì) get 或者 __contains__(in 運(yùn)算符會(huì)用到這個(gè)方法)這些方法的使用沒有影響。這也是我在上面中提到,defaultdict 中的 default_factory 只對(duì) __getitem__ 有作用的原因。
如果要自定義一個(gè)映射類型,更合適的策略其實(shí)是繼承 collections.UserDict 類。這里我們從 dict 繼承,只是為了演示__missing__ 是如何被 dict.__getitem__ 調(diào)用的。(直接繼承dict是有一些問題的,以后再說)
# -*- coding: utf-8 -*-
class StrKeyDict0(dict): # StrKeyDict0 繼承了 dict。
def __missing__(self, key):
if isinstance(key, str): # 如果找不到的鍵本身就是字符串,那就拋出 KeyError 異常。
raise KeyError(key)
return self[str(key)] # 如果找不到的鍵不是字符串,那么把它轉(zhuǎn)換成字符串再進(jìn)行查找
def get(self, key, default=None):
try:
# get 方法把查找工作用 self[key] 的形式委托給 __getitem__,這樣在宣布查找失敗之
# 前,還能通過 __missing__ 再給某個(gè)鍵一個(gè)機(jī)會(huì)。
return self[key]
except KeyError:
return default # 如果拋出 KeyError,那么說明 __missing__ 也失敗了,于是返回 default。
def __contains__(self, key):
# 先按照傳入鍵的原本的值來查找(我們的映射類型中可能含有非字符串的鍵),如果沒
# 找到,再用 str() 方法把鍵轉(zhuǎn)換成字符串再查找一次。
return key in self.keys() or str(key) in self.keys()
if __name__ == '__main__':
d = StrKeyDict0([('2', 'two'), ('4', 'four')])
print(d['2']) # two
print(d[1])
# Traceback (most recent call last):
# File "C:/myFiles/company_project/xbot/tests/miss_test.py", line 27, in <module>
# print(d[1])
# File "C:/myFiles/company_project/xbot/tests/miss_test.py", line 8, in __missing__
# return self[str(key)] # 如果找不到的鍵不是字符串,那么把它轉(zhuǎn)換成字符串再進(jìn)行查找
# File "C:/myFiles/company_project/xbot/tests/miss_test.py", line 7, in __missing__
# raise KeyError(key)
# KeyError: '1'
print(2 in d)
# True
print(1 in d)
# False
注意:
- 下面來看看為什么 isinstance(key, str) 測(cè)試在上面的 __missing__ 中是必需的。
- 如果沒有這個(gè)測(cè)試,只要 str(k) 返回的是一個(gè)存在的鍵,那么 __missing__ 方法是沒問題的,不管是字符串鍵還是非字符串鍵,它都能正常運(yùn)行。但是如果 str(k) 不是一個(gè)存在的鍵,代碼就會(huì)陷入無限遞歸。這是因?yàn)?nbsp;__missing__ 的最后一行中的 self[str(key)] 會(huì)調(diào)用 __getitem__,而這個(gè) str(key) 又不存在,于是 __missing__ 又會(huì)被調(diào)用。
- 為了保持一致性,__contains__ 方法在這里也是必需的。這是因?yàn)?nbsp;k in d 這個(gè)操作會(huì)調(diào)用它,但是我們從 dict 繼承到的__contains__ 方法不會(huì)在找不到鍵的時(shí)候調(diào)用 __missing__方法。__contains__ 里還有個(gè)細(xì)節(jié),就是我們這里沒有用更具 Python 風(fēng)格的方式——k in my_dict——來檢查鍵是否存在,因?yàn)槟且矔?huì)導(dǎo)致 __contains__ 被遞歸調(diào)用。為了避免這一情況,這里采取了更顯式的方法,直接在這個(gè) self.keys() 里查詢。
- 像 k in my_dict.keys() 這種操作在 Python 3 中是很快的,而且即便映射類型對(duì)象很龐大也沒關(guān)系。這是因?yàn)?nbsp;dict.keys() 的返回值是一個(gè)“視圖”。視圖就像一個(gè)集合,而且跟字典類似的是,在視圖里查找一個(gè)元素的速度很快。在“Dictionary view objects”(https://docs.python.org/3/library/stdtypes.html#dictionary-view-objects)里可以找到關(guān)于這個(gè)細(xì)節(jié)的文檔。Python 2 的 dict.keys() 返回的是個(gè)列表,因此雖然上面的方法仍然是正確的,它在處理體積大的對(duì)象的時(shí)候效率不會(huì)太高,因?yàn)?nbsp;k in my_list 操作需要掃描整個(gè)列表。
到此這篇關(guān)于python 字典d[k]中key不存在的解決方案的文章就介紹到這了,更多相關(guān)python 字典d[k]中key不存在內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python測(cè)試框架:pytest學(xué)習(xí)筆記
這篇文章主要介紹了Python測(cè)試框架:pytest的相關(guān)資料,幫助大家更好的利用python進(jìn)行單元測(cè)試,感興趣的朋友可以了解下2020-10-10
Python獲取電腦硬件信息及狀態(tài)的實(shí)現(xiàn)方法
這篇文章主要介紹了Python獲取電腦硬件信息及狀態(tài)的實(shí)現(xiàn)方法,是一個(gè)很實(shí)用的技巧,需要的朋友可以參考下2014-08-08

