collections 模块中 defaultdict 和普通 dict 有什么区别?
Python 中的 collections.defaultdict 是内置 dict(字典)的一个子类。它们最大的区别在于 如何处理不存在的键(Missing Keys)。
以下是详细的对比分析:
1. 核心区别:访问不存在的键
普通
dict:- 如果你试图访问一个不存在的键(例如
d['key']),Python 会抛出KeyError异常。 - 你需要先检查键是否存在,或者使用
.get()方法来避免报错。
- 如果你试图访问一个不存在的键(例如
defaultdict:- 如果你访问一个不存在的键,它 不会报错。
- 相反,它会调用初始化时传入的
default_factory(工厂函数),生成一个默认值,将该值赋给这个键,并返回这个值。
2. 代码对比
让我们通过两个最常见的场景来看看代码写法的不同。
场景 A:计数(Counting)
假设我们要统计一个列表中每个单词出现的次数。
普通 dict 写法:
python
words = ['apple', 'banana', 'apple', 'orange']
counts = {}
for word in words:
if word in counts:
counts[word] += 1
else:
counts[word] = 1 # 必须手动初始化
# 或者使用 .get()
# counts[word] = counts.get(word, 0) + 1
defaultdict 写法:
python
from collections import defaultdict
words = ['apple', 'banana', 'apple', 'orange']
# int() 函数被调用时返回 0
counts = defaultdict(int)
for word in words:
counts[word] += 1 # 不需要检查,直接加
# 第一次遇到 'apple' 时,counts['apple'] 自动变为 0,然后 +1
场景 B:分组(Grouping)
假设我们要把一个元组列表按类别分组。
普通 dict 写法:
python
data = [('fruit', 'apple'), ('fruit', 'banana'), ('veg', 'carrot')]
grouped = {}
for category, item in data:
if category not in grouped:
grouped[category] = [] # 必须手动创建一个空列表
grouped[category].append(item)
# 或者使用 setdefault
# grouped.setdefault(category, []).append(item)
defaultdict 写法:
python
from collections import defaultdict
data = [('fruit', 'apple'), ('fruit', 'banana'), ('veg', 'carrot')]
# list() 函数被调用时返回空列表 []
grouped = defaultdict(list)
for category, item in data:
grouped[category].append(item) # 直接 append,键不存在时自动创建空列表
3. 初始化方式
普通
dict:pythond = {} # 或 d = dict()defaultdict:
你需要传入一个可调用的对象(函数或类)作为default_factory。pythonfrom collections import defaultdict d_int = defaultdict(int) # 默认值是 0 d_list = defaultdict(list) # 默认值是 [] d_set = defaultdict(set) # 默认值是 set() d_str = defaultdict(str) # 默认值是 "" d_custom = defaultdict(lambda: "Unknown") # 默认值是 "Unknown"
4. defaultdict 的一个潜在陷阱(副作用)
使用 defaultdict 时要小心,只要你访问了一个不存在的键,它就会被创建并加入字典中。
python
d = defaultdict(int)
print(d) # defaultdict(<class 'int'>, {})
# 仅仅是打印或检查某个键
print(d['non_existent_key'])
# 此时字典里已经多了一个键!
print(d) # defaultdict(<class 'int'>, {'non_existent_key': 0})
而在普通 dict 中,如果你使用 d.get('key'),虽然返回 None,但不会改变字典本身。
5. 总结
| 特性 | 普通 dict |
collections.defaultdict |
|---|---|---|
| 访问不存在的键 | 抛出 KeyError |
调用工厂函数生成默认值,不报错 |
| 代码简洁度 | 需处理 if key in d 或用 .get() |
代码更少,逻辑更直接 |
| 初始化 | d = {} |
d = defaultdict(type) |
| 性能 | 使用 .setdefault() 较慢 |
通常比使用 dict.setdefault() 更快 |
| 适用场景 | 只需要简单的键值存储,或需要严格检查键是否存在 | 计数、分组、构建嵌套数据结构 |
什么时候用哪个?
- 如果你在做累加、聚合、分组操作,
defaultdict是更好的选择,代码更优雅且效率高。 - 如果你只是查表,或者你不希望字典因为错误的查询而自动产生垃圾数据,请使用 普通
dict。