

Python 中 Defaultdict 的理解
source link: https://www.lfhacks.com/tech/python-defaultdict/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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 "<stdin>", line 1, in <module>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)>>> adefaultdict(<function d at 0x000001BD3751DF70>, {})
访问一个不存在的key,这时自动生成 {1: 'default'}
的键值对
>>> a[1]'default'
这一步的过程是:
- [1] 语法调用
__getitem__(1)
__getitem__(1)
发现不存在1
这个键__getitem__(1)
调用__missing__()
__missing__()
不带参数调用函数d()
,得到默认值'default'
__missing__()
将不存在的键1
和默认值'default'
组成键值对,插入到自身对象中
查看对象的变化
>>> adefaultdict(<function d at 0x000001BD3751DF70>, {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>>> adefaultdict(<function d.<locals>.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)]>>>
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK