collections.defaultdict 是 dict.setdefault() 基础上发展而来的。 首先回顾 dict 类型最基本的取值方法:方括号 [] __getitem__() 方法 只要在自定义类里定义了这个方法,那么实例化出来的对象就拥有了 [] 取值的能力。 >>> class A: ... def __getitem__(self, index): ... return 'get item.' ... >>> a=A() >>> a[1] 'get item.' >>> a['a'] 'get item.' >>> 如果是 dict 对象,用 [] 访问的 key 不存在,就会直接抛出 KeyError 异常。 >>> d={'a':1} >>> d['b'] Traceback (most recent call last): File "", line 1, in KeyError: 'b' >>> 为了更优雅、更方便的处理这种异常场景,发展出了 get() 和 setdefault() 函数 get() 和 setdefault() get() 提供了返回默认值功能 setdefault() 在 get() 基础上,提供了将默认值(default)插回(set)字典的功能。这就是 setdefault 名称的由来。 >>> d={'a':1} >>> d.setdefault('b',2) 2 >>> d {'a': 1, 'b': 2} >>> 但是每次调用 setdefault 函数,又不如 [] 提供的方式优雅,所以希望在 [] 取值方式中实现 setdefault 的功能。 # 想象中的样子 >>> d={'a':1} >>> d['b'] # 不报错,直接创建默认值 >>> d {'a': 1, 'b': 默认值} 于是需要改造 dict 类型,从 dict 派生出新类来实现这种需求。有下面两种方法: 自己编写 dict 的派生类 使用已有的 collections.defaultdict 这两种方法改造的关键点都是重(chóng)写 __missing__() 特殊方法。 __missing__() 方法 当基类 dict 发现给出的键不存在时,都会调用 __missing__() 方法。 虽然 dict 并没有定义这个方法,但是不妨碍它知道 __missing__() 这么个方法存在。 如果向子类的 __getitem__(key) 提供的 key 不存在的时候,就会自动的调用 __missing__()方法,同时不会抛出 KeyError 异常。 如果在 __missing__() 方法中设置一个动作,即向自己插入一个默认值,就实现了 这一节 最末希望实现的样子。 这里插入的默认值,是用一个工厂方法 default_factory 实现的,由 __missing__() 方法调用。 default_factory default_factory 是一个 callable 对象,可以是一个函数或者类,当 __getitem__(key) 的 key 不存在时,default_factory 会被 __missing__() 方法调用,用于生成那个不存在的 key 对应的默认值。 在 __missing__() 方法调用 default_factory 的时候,是不带任何参数的。 所以 default_factory 应该有以下这种行为: >>> default_factory() 默认值 >>> 满足上述行为的,可以是以下这些对象: 自定义的函数 内置类型 int、list、str、set 自定义的类 理解了以上这些概念,就很好理解 collections.defaultdict 的用法了 defaultdict collections.defaultdict 是内置字典类型 dict 的一个派生类,和 dict 类的区别在于: 重写了一个方法(__missing__()) 增加了一个位置参数(default_factory) 其余使用方法与 dict 完全相同。增加的 default_factory 参数用于生成默认值。 defaultdict(default_factory=None, /[, ...]) default_factory 的理念和可以取的值在 这一节 中介绍。 例子 基本的初始化方法 defaultdict 在最开始位置增加了 default_factory 参数用于生成默认值: 首先导入模块 >>> from collections import defaultdict 构造一个函数用于返回默认值 >>> def d(): ... return 'default' ... 实例化 defaultdict >>> a=defaultdict(d) >>> a defaultdict(, {}) 访问一个不存在的key,这时自动生成 {1: 'default'} 的键值对 >>> a[1] 'default' 这一步的过程是: [1] 语法调用 __getitem__(1) __getitem__(1) 发现不存在 1 这个键 __getitem__(1) 调用 __missing__() __missing__() 不带参数调用函数 d(),得到默认值'default' __missing__() 将不存在的键 1 和默认值'default'组成键值对,插入到自身对象中 查看对象的变化 >>> a defaultdict(, {1: 'default'}) >>> 这时已经有了 {1: 'default'} 的记录。 这样就是达到了用 [] 的语法实现 setdefault() 函数的目的。 其他形式的 default_factory 上一节 中介绍了自定义函数作为 default_factory 的例子,在 这一节 中提到,还有另外两种 default_factory 的形式: 内置类型 int、list、str、set 自定义的类 内置类型作为可调用对象(callable),返回的是空的默认值: >>> int() 0 >>> str() '' >>> list() [] >>> set() set() >>> 在一些统计的场合,可以直接用作 default_factory 这里举个来自标准库文档里归类的例子 s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)] d = defaultdict(list) for k, v in s: d[k].append(v) sorted(d.items()) # [('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])] 在第4行中,如果 d 中没有 k,则新建一个 {k: []} 的记录,并且返回空列表 [] 的引用,仿佛存在 k 一般。 所以,无论 d 中有没有 k,都使得循环体能够持续执行。整个代码非常简洁、易读。 如果用 setdefault(),第4行将成为: d.setdefault(k, []).append(v) 工厂函数 如果像 这一节 中自定义函数用作 default_factory ,只能返回一个固定值。如果希望将这个固定值参数化,可以使用工厂函数的方法。 普通函数: def d(): return 'default' 工厂函数: def d(count): def f(): return count return f 工厂函数返回的是一个函数,这样无参数调用 d(count) ,实质上调用的是 f() >>> a=defaultdict(d(10)) >>> a[1] 10 >>> a defaultdict(.f at 0x1076e4550>, {1: 10}) >>> 工厂函数还能简单地写成匿名 lambda 函数: def d(count): return lambda: count 效果和上面的一致。 其他初始化方法 先看 dict 类型的用法,有三种初始化的方法: # dict() >>> dict() {} >>> # dict(**kwarg) >>> dict(one=1, two=2, three=3) {'one': 1, 'two': 2, 'three': 3} >>> # dict(mapping, **kwarg) >>> dict({'one': 1, 'two': 2, 'three': 3}) {'one': 1, 'two': 2, 'three': 3} >>> # dict(iterable, **kwarg) >>> dict([('two', 2), ('one', 1), ('three', 3)]) {'two': 2, 'one': 1, 'three': 3} >>> 既然 defaultdict 继承了 dict 的行为,那么上述 dict 的初始化方法也都适用。 >>> def d(count): ... return lambda: count ... >>> a=defaultdict(d(4), one=1, two=2, three=3) >>> a['four'] 4 >>> list(a.items()) [('one', 1), ('two', 2), ('three', 3), ('four', 4)] >>>