Python中setdefault和defaultdict的应用示例

也许每个 Python 程序员都知道可以用 d.get(k, default) 来代替 d[k],给找不到的键一个默认的返回值(这比处理 KeyError 要方便不少)。

但是要更新某个键对应的值的时候,不管使用 __getitem__ 还是 get 都会不自然,而且效率低。

dict.get 并不是处理找不到的键的最好方法。而是应该用setdefault或defaultdict处理找不到的键


示例代码:

test.py 里面代码如下

# -*- coding: utf-8 -*-
import sys
import re
WORD_RE = re.compile(r'\w+')
index = {}
with open(sys.argv[1]) as fp:
for line_no, line in enumerate(fp, 1):
for match in WORD_RE.finditer(line):
word = match.group()
column_no = match.start() + 1
location = (line_no, column_no)
# target
occurrences = index.get(word, [])
occurrences.append(location)
index[word] = occurrences
# 以字母顺序打印出结果
# sorted 函数的 key= 参数没有调用 str.uppper,而是把这个方法的引用传递
# 给 sorted 函数,这样在排序的时候,单词会被规范成统一格式。
for word in sorted(index, key=str.upper):
print(word, index[word])

先生成zen.txt文本文件:

python -c "import this" > zen.txt

运行代码:

python test.py zen.txt

输出结果(部分)如下:

('a', [(19, 48), (20, 53)])
('Although', [(11, 1), (16, 1), (18, 1)])
('ambiguity', [(14, 16)])
('and', [(15, 23)])
('are', [(21, 12)])
('aren', [(10, 15)])
('at', [(16, 38)])
('bad', [(19, 50)])
('be', [(15, 14), (16, 27), (20, 50)])
('beats', [(11, 23)])
('Beautiful', [(3, 1)])
...

示例讲解:

这段代码是实现读取文本文件,列出文本中所有的单词在文本中出现的地方。
单词出现的地方用列表表示,列表元素是元组,元组存储单词出现的行和列数。

target三行代码是主要实现:从索引获取单词的列表,将找到的单词出现的地方加入列表并更新索引。

target三行代码效率分析:
查找单词在索引里的值,一次查询;新单词加入列表后更新索引,又一次查询。
查询了两次。

target代码更好的实现,使用setdefault:
三行代码替换成一行

index.setdefault(word, []).append(location)

用 setdefault 只需要一次就可以完成整个操作! 简洁又高效,Pythonic !

上面这行代码的效果跟以下一样:

if key not in my_dict:
my_dict[key] = []
my_dict[key].append(new_value)

只不过后者至少要进行两次键查询——如果键不存在的话,就是三次,用 setdefault 只需要一次就可以完成整个操作。

target代码更好的实现,使用defaultdict:
三行代码替换成一行

index[word].append(location)

但index要改成defaultdict:

import collections
# 把 list 构造方法作为 default_factory 来创建一个 defaultdict。
index = collections.defaultdict(list)

如果 index 并没有 word 的记录,那么 default_factory 会被调用,为查询不到的键创造一个值。这个值在这里是一个空的列表,然后这个空列表被赋值给 index[word],继而被当作返回值返回,因此 .append(location) 操作总能成功。如果在创建defaultdict 的时候没有指定 default_factory,查询不存在的键会触发 KeyError。

defaultdict 里的 default_factory 只会在 __getitem__ 里被调用,在其他的方法里完全不会发挥作用。比如,dd 是个 defaultdict,k 是个找不到的键, dd[k] 这个表达式会调用 default_factory 创造某个默认值,而 dd.get(k) 则会返回 None。

参考:
这个示例代码来自《流畅的Python》第三章。很棒的示例代码!不仅是setdefault和defaultdict,还有其它知识点,如finditer、enumerate、re、sorted、sys.argv等。