【Python】详解 collections.defaultdict

目录

一、绪论

二、defaultdict 类

2.1 说明

2.2 用例

2.2.1 整合字典

2.2.2 计数字典

2.2.3 不重复计数字典

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

https://docs.python.org/zh-cn/3.6/library/collections.html?highlight=defaultdict#collections.defaultdict

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章