目录
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