目錄
2.2.4 更靈活的方式 —— 使用 lambda 提供各種類型的默認 value
一、緒論
collections 作爲 Python 的內建集合模塊,實現了許多十分高效的特殊容器數據類型,即除了 Python 通用內置容器: dict、list、set 和 tuple 等的替代方案。在 IDLE 輸入 help(collections) 可查看幫助文檔,其中常見的類/函數如下:
名稱 | 功能 |
namedtuple | 用於創建具有命名字段的 tuple 子類的 factory 函數 (具名元組) |
deque | 類似 list 的容器,兩端都能實現快速 append 和 pop (雙端隊列) |
ChainMap | 類似 dict 的類,用於創建多個映射的單視圖 |
Counter | 用於計算 hashable 對象的 dict 子類 (可哈希對象計數) |
OrderedDict | 記住元素添加順序的 dict 子類 (有序字典) |
defaultdict | dict 子類調用 factory 函數來提供缺失值 |
UserDict | 包裝 dict 對象以便於 dict 的子類化 |
UserList | 包裝 list 對象以便於 list 的子類化 |
UserString | 包裝 string 對象以便於 string 的子類化 |
而本文詳述的對象爲默認字典 —— defaultdict 。
二、defaultdict 類
2.1 說明
class collections.defaultdict([default_factory[, ...]])
defaultdict,顧名思義是默認字典,它返回一個類似 dict 的新對象。defaultdict 作爲內置 dict 類的子類,重載了一個新方法並添加了一個可寫的實例變量,而其餘功能則與 dict 類相同。
第一個參數 default_factory 是工廠函數 (factory function),默認爲 None,可選 list、tuple、set 、str 、int 等用於創建各種類型對象的內建函數 (工廠),其作用在於:當用 defaultdict 創建的默認字典的 key 不存在時,將返工廠函數 default_factory 的默認值。例如,list 對應 [ ]、tuple 對應 ()。而其他參數用法則同 dict,包括關鍵詞參數。
通常,我們創建 dict 時,若索引了不存在的 key,則會導致 KeyError:
>>> dict0 = {}
>>> dict0[0]
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
dict0[0]
KeyError: 0
爲避免這樣的問題,可以對默認字典 defaultdict 饋入指定“工廠” 作爲默認工廠,當訪問不存在的 key 時,會調用默認工廠自動創建並返回一個默認 value,而非 KeyError,例如:
>>> from collections import defaultdict
## dict 初始化之初, key=0 原本是不存在的,
## 但在 defaultdict 的作用下, 根據工廠函數構建了相應的 默認 value
# -------------------------------------------------------------
>>> dict1 = defaultdict(list)
>>> dict1
defaultdict(<class 'list'>, {})
>>> dict1[0]
[]
>>> dict1
defaultdict(<class 'list'>, {0: []})
# -------------------------------------------------------------
>>> dict2 = defaultdict(tuple)
>>> dict2
defaultdict(<class 'tuple'>, {})
>>> dict2[0]
()
>>> dict2
defaultdict(<class 'tuple'>, {0: ()})
# -------------------------------------------------------------
>>> dict3 = defaultdict(set)
>>> dict3
defaultdict(<class 'set'>, {})
>>> dict3[0] # 注意, 爲區別 dict 的 {}, set 創建的是 set()
set()
>>> dict3
defaultdict(<class 'set'>, {0: set()})
# -------------------------------------------------------------
>>> dict4 = defaultdict(str)
>>> dict4
defaultdict(<class 'str'>, {})
>>> dict4[0]
''
>>> dict4
defaultdict(<class 'str'>, {0: ''})
# -------------------------------------------------------------
>>> dict5 = defaultdict(int)
>>> dict5
defaultdict(<class 'int'>, {})
>>> dict5[0]
0
>>> dict5
defaultdict(<class 'int'>, {0: 0})
# -------------------------------------------------------------
>>> dict6 = defaultdict(float)
>>> dict6
defaultdict(<class 'float'>, {})
>>> dict6[0]
0.0
>>> dict6
defaultdict(<class 'float'>, {0: 0.0})
注意,上述例子的實參是 list、tuple、set、str、int、float,它們均爲類對象,而非新列表 list()、新元組 tuple() 等等。
當然,形參工廠函數 default_factory 可接受的工廠實參遠不止這些。
但是,若實例化 defaultdict 對象時不指定 default_factory 參數,則默認爲 None,使之與 dict 基本沒有區別,訪問不存在的 key 時同樣會拋出 KeyError,而不會創建默認 value (因爲沒有默認工廠可用!):
>>> dict00 = defaultdict()
>>> dict00
defaultdict(None, {})
>>> dict00[0]
2.2 用例
2.2.1 整合字典
例如,使用 defaultdict 能夠很容易地將 tuple-list 整合爲 value 爲 list 的 dict,而不必預先檢查 key 的存在性:
>>> old = [('yellow', 2), ('blue', 4), ('yellow', 1), ('blue', 1), ('red', 2)]
>>> new = defaultdict(list)
>>> for key, value in old:
new[key].append(value)
>>> new.items()
dict_items([('yellow', [2, 1]), ('blue', [4, 1]), ('red', [2])])
>>> sorted(new.items())
[('blue', [4, 1]), ('red', [2]), ('yellow', [2, 1])]
使用了 defaultdict 的方法比使用 dict.setdefault() 的等價方法更加簡潔高效:
>>> old = [('yellow', 2), ('blue', 4), ('yellow', 1), ('blue', 1), ('red', 2)]
>>> new2 = {}
>>> for key, value in old:
new2.setdefault(key, []).append(value)
>>> new2.items()
dict_items([('yellow', [2, 1]), ('blue', [4, 1]), ('red', [2])])
>>> sorted(new2.items())
[('blue', [4, 1]), ('red', [2]), ('yellow', [2, 1])]
2.2.2 計數字典
例如,使用 defaultdict 爲 string 中各元素出現次數計數:
>>> string = 'nobugshahaha'
>>> count = defaultdict(int)
>>> for key in string:
count[key] += 1
>>> count.items()
dict_items([('n', 1), ('o', 1), ('b', 1), ('u', 1), ('g', 1), ('s', 1), ('h', 3), ('a', 3)])
>>> sorted(count.items())
[('a', 3), ('b', 1), ('g', 1), ('h', 3), ('n', 1), ('o', 1), ('s', 1), ('u', 1)]
但對於計數,我還是傾向於使用專業的計數器類 collections.Counter():
from collections import Counter
>>> count2 = Counter(string)
>>> count2
Counter({'h': 3, 'a': 3, 'n': 1, 'o': 1, 'b': 1, 'u': 1, 'g': 1, 's': 1})
>>> count2.items()
dict_items([('n', 1), ('o', 1), ('b', 1), ('u', 1), ('g', 1), ('s', 1), ('h', 3), ('a', 3)])
>>> count2.most_common()
[('h', 3), ('a', 3), ('n', 1), ('o', 1), ('b', 1), ('u', 1), ('g', 1), ('s', 1)]
2.2.3 不重複計數字典
使用 defaultdict 構造 set-dict 實現元素的不重複計數:
>>> colors = [('yellow', 2), ('blue', 4), ('yellow', 1), ('blue', 4), ('yellow', 2)]
>>> color_set = defaultdict(set)
>>> for key, value in colors:
color_set[key].add(value)
>>> color_set.items()
dict_items([('yellow', {1, 2}), ('blue', {4})])
>>> sorted(color_set.items())
[('blue', {4}), ('yellow', {1, 2})]
2.2.4 更靈活的方式 —— 使用 lambda 提供各種類型的默認 value
注意這裏面的用法有一點點高級,有興趣者看:
>>> def constant_factory(value):
''' 使用 lambda 函數提供任何常量值 '''
return lambda: value # 不常見用法
>>> words = defaultdict(constant_factory('~<China>~'))
>>> words
defaultdict(<function constant_factory.<locals>.<lambda> at 0x0000025698DF37B8>, {})
>>> words.items()
dict_items([])
>>> words.update(name='Liangzai', action='ran')
>>> words.items()
dict_items([('name', 'Liangzai'), ('action', 'ran')])
>>> "%(name)s %(action)s to %(object)s" % words # 不常見用法
'Liangzai ran to ~<China>~'
參考文獻:
《Think Python 2 Edition》—— Allen B. Downey