defaultdict : 无条件字典的进化

虽然Python有许多外部库,但仅仅理解标准库就足以编写强大的实用代码。本文将深入探讨其中的 collections.defaultdict

通过这篇文章,您将超越简单的概念介绍,明确理解 defaultdict 应该在何时、为何以及如何使用。

如果您对作为collections系列第一的Counter类感兴趣,建议阅读上一篇文章。 Python标准库 ① - collections.Counter


1. 基本概念: defaultdict是什么?

Tux with defaultdicts

defaultdict是Python标准库 collections模块中包含的特殊字典(dict)的子类。普通字典访问不存在的键时会抛出 KeyError,但 defaultdict 可以指定自动生成默认值的函数(工厂函数),这样代码会更加简洁并避免错误。


2. 基本用法

from collections import defaultdict

d = defaultdict(int)
d['apple'] += 1
print(d)  # defaultdict(<class 'int'>, {'apple': 1})

这里 int() 返回默认值 0。访问不存在的键 'apple' 时,自动生成 0,然后执行 +1


3. 多种默认值示例

from collections import defaultdict

# 默认值: 0 (int)
counter = defaultdict(int)
counter['a'] += 1
print(counter)  # defaultdict(<class 'int'>, {'a': 1})

# 默认值: 空列表
group = defaultdict(list)
group['fruit'].append('apple')
group['fruit'].append('banana')
print(group)  # defaultdict(<class 'list'>, {'fruit': ['apple', 'banana']})

# 默认值: 空集合
unique_tags = defaultdict(set)
unique_tags['tags'].add('python')
unique_tags['tags'].add('coding')
print(unique_tags)  # defaultdict(<class 'set'>, {'tags': {'python', 'coding'}})

# 默认值: 自定义初始值
fixed = defaultdict(lambda: 100)
print(fixed['unknown'])  # 100

4. 实战示例

1. 统计单词频率

words = ['apple', 'banana', 'apple', 'orange', 'banana']
counter = defaultdict(int)

for word in words:
    counter[word] += 1

print(counter)
# defaultdict(<class 'int'>, {'apple': 2, 'banana': 2, 'orange': 1})

👉 Counter vs defaultdict
单词频率统计更适合使用 collections.Counter(),在需要进行统计或排名分析的情况下,使用 Counter 是更为合适的。但对于简单累计计数, defaultdict(int) 使用起来也相当简便。

2. 按组整理日志

logs = [
    ('2024-01-01', 'INFO'),
    ('2024-01-01', 'ERROR'),
    ('2024-01-02', 'DEBUG'),
]

grouped = defaultdict(list)
for date, level in logs:
    grouped[date].append(level)

print(grouped)
# defaultdict(<class 'list'>, {'2024-01-01': ['INFO', 'ERROR'], '2024-01-02': ['DEBUG']})

3. 整理去重标签

entries = [
    ('post1', 'python'),
    ('post1', 'coding'),
    ('post1', 'python'),
]

tags = defaultdict(set)
for post, tag in entries:
    tags[post].add(tag)

print(tags)
# defaultdict(<class 'set'>, {'post1': {'python', 'coding'}})

5. 注意事项

  • defaultdict内部存储了默认值生成器,因此在 repr() 时可能与普通 dict 的表现不同。
  • 在JSON序列化时可能会出现问题。使用 dict(d) 转换后处理是安全的。
  • 默认值只在访问 [] 时生成,而如果通过 get() 访问,则不会生成默认值。
from collections import defaultdict

d = defaultdict(list)
print(d.get('missing'))  # None
print(d['missing'])      # []

6. 何时使用? – defaultdict的三大优势

defaultdict在大量使用 dict + 条件语句模式的情况下,同时提高了可读性、可维护性和安全性。尤其在以下三种情况下,您会感觉“哦,这个肯定更适合用defaultdict!”

6-1. 无条件计数/累计的聚合代码

from collections import defaultdict

# 普通 dict
counts = {}
for item in items:
    if item not in counts:
        counts[item] = 0
    counts[item] += 1

# defaultdict
counts = defaultdict(int)
for item in items:
    counts[item] += 1

✔ 条件语句消失,代码变得简洁,错误的可能性减少了。
✔ 特别适合日志分析、单词计数等大数据处理。

6-2. 当累积列表/集合时替代 setdefault

from collections import defaultdict

# 普通 dict
posts = {}
for tag, post in data:
    if tag not in posts:
        posts[tag] = []
    posts[tag].append(post)

# defaultdict
posts = defaultdict(list)
for tag, post in data:
    posts[tag].append(post)

✔ 比 setdefault() 更直观,在循环中也更简洁。
✔ 适用于数据分组的结构。

6-3. 自动化嵌套字典的初始化

# 普通字典
matrix = {}
if 'x' not in matrix:
    matrix['x'] = {}
matrix['x']['y'] = 10

# defaultdict 嵌套
matrix = defaultdict(lambda: defaultdict(int))  # 每当没有键时都会自动生成 defaultdict(int) 的实例
matrix['x']['y'] += 10

✔ 可以轻松创建嵌套数据结构,对多维字典操作非常有利
✔ 在数据挖掘、解析、树结构存储等方面表现出强大的优势。

lambda: defaultdict(int) 在内部结构上为每当没有键时返回 defaultdict(int),自动生成嵌套的字典。


7. 总结

collections.defaultdict 对于初学者来说,可能只是简单的 dict 扩展,但随着使用的深入,您会体会到它是让代码更明晰和安全的结构性工具

  • 可以不担心 KeyError 使用字典。
  • 无需条件语句即可对数据进行分组和累积。
  • 可以直观地构建嵌套字典。
# 通过 defaultdict 安全且简洁地处理的示例
salaries = defaultdict(int)
for dept, amount in records:
    salaries[dept] += amount

如果您能用一行代码来防止错误 + 提高可读性 + 增强可维护性,
那么 defaultdict 就不仅仅是简单的便利功能,而是Python的思维方式的核心工具

接下来讨论的主题是 pathlib
这种现代的方法将文件/目录处理的代码变得面向对象,从初学者到中级开发者都能受益。熟悉 os.path 的开发者会感到“哇!比 os.path 简单多了!”期待您的下篇文章。