Python 进阶:全面精通 collections.defaultdict
在 Python 编程中,处理字典(dict)时最常遇到的“烦恼”之一就是 KeyError。当尝试访问一个尚不存在的键时,Python 会立即抛出这个错误。
这在计数或分组等场景下尤为不便,因为总是需要写下面这样的防御性代码:
# 繁琐的常规 dict 写法
counts = {}
key = 'some_key'
if key not in counts:
counts[key] = 0 # 必须先手动初始化
counts[key] += 1
为了解决这个问题,Python 的 collections 模块提供了一个强大的子类:defaultdict。
核心:defaultdict 为什么存在?
defaultdict 的核心功能是:在访问不存在的键时,它会自动调用一个“默认工厂函数”来创建并返回一个默认值,从而彻底避免 KeyError。
它继承自 dict,所以它拥有常规字典的所有方法(如 .keys(), .values(), .items()),但它在初始化时多接受一个参数:
from collections import defaultdict
# 语法:defaultdict(default_factory)
这个 default_factory 是一个可调用对象(如函数、类型构造函数 list 或 int,或 lambda 表达式),它会在需要时被调用,且不带任何参数。
工作原理:魔法揭秘
当使用 defaultdict(factory) 创建一个字典 d 并执行 d[key] 时:
-
Python 检查
key是否已经在d中。 -
如果
key存在:defaultdict的行为与常规dict完全相同,返回d[key]的值。 -
如果 key 不存在(魔法发生的地方):
a. defaultdict 会调用提供的 default_factory(例如,int() 或 list())。
b. default_factory 返回一个默认值(例如,0 或 [])。
c. defaultdict 将这个默认值赋给 d[key]。
d. 最后,它返回这个新创建的默认值。
三大经典用例
defaultdict 的美妙之处在于其简洁性。让我们来看看它最常见的三个用法。
1. 计数 (Counting): defaultdict(int)
这是 defaultdict 最著名的用法。当需要统计一系列项目中各项出现的次数时使用它。
-
默认工厂:
int -
工厂返回值:
int()会返回0。
示例:统计单词频率
from collections import defaultdict
sentence = "the quick brown fox jumps over the lazy dog"
word_counts = defaultdict(int) # 当键不存在时,调用 int() 返回 0
for word in sentence.split():
# 如果 word 第一次出现 (如 'quick'):
# 1. word_counts['quick'] 不存在
# 2. 调用 int() -> 得到 0
# 3. word_counts['quick'] 被设为 0
# 4. 执行 0 + 1
# 如果 word 已存在 (如 'the'):
# 1. word_counts['the'] 存在 (值为 1)
# 2. 执行 1 + 1
word_counts[word] += 1
print(word_counts)
# 输出: defaultdict(<class 'int'>,
# {'the': 2, 'quick': 1, 'brown': 1, 'fox': 1, 'jumps': 1, 'over': 1, 'lazy': 1, 'dog': 1})
对比常规 dict: defaultdict(int) 省去了所有 if key not in ... 的检查。
提示:defaultdict(int) 这种计数功能,其实还有一个更专门的“近亲”叫作 Counter。
2. 分组 (Grouping): defaultdict(list)
当需要将数据项按类别收集到列表中时使用它。
-
默认工厂:
list -
工厂返回值:
list()会返回一个空列表[]。
示例:将学生按年级分组
from collections import defaultdict
students = [
('Alice', 'Seniors'),
('Bob', 'Juniors'),
('Charlie', 'Seniors'),
('David', 'Freshmen'),
('Eve', 'Juniors'),
]
# 按年级分组,值应该是 list
students_by_grade = defaultdict(list)
for name, grade in students:
# 如果 grade 第一次出现 (如 'Seniors'):
# 1. students_by_grade['Seniors'] 不存在
# 2. 调用 list() -> 得到 []
# 3. students_by_grade['Seniors'] 被设为 []
# 4. 执行 [].append('Alice')
students_by_grade[grade].append(name)
print(students_by_grade)
# 输出: defaultdict(<class 'list'>,
# {'Seniors': ['Alice', 'Charlie'], 'Juniors': ['Bob', 'Eve'], 'Freshmen': ['David']})
3. 收集唯一项 (Grouping Unique Items): defaultdict(set)
与 defaultdict(list) 类似,但如果你只关心每个类别中有哪些唯一的成员(不关心顺序或重复),可以使用 set。
-
默认工厂:
set -
工厂返回值:
set()会返回一个空集合set()。
示例:记录哪些用户访问了哪些页面
from collections import defaultdict
access_log = [
('/home', 'user1'),
('/login', 'user2'),
('/home', 'user3'),
('/home', 'user1'), # user1 重复访问
('/profile', 'user2'),
]
# 页面 -> 访问过的唯一用户
page_visitors = defaultdict(set)
for page, user in access_log:
# 使用 .add() 而不是 .append()
page_visitors[page].add(user)
print(page_visitors)
# 输出: defaultdict(<class 'set'>,
# {'/home': {'user1', 'user3'}, '/login': {'user2'}, '/profile': {'user2'}})
# 注意:/home 只有 {'user1', 'user3'},重复的 'user1' 被 set 自动处理了
🚀 高级技巧:lambda 与自定义工厂
default_factory 必须是一个不带参数的调用。如果你需要一个更复杂的默认值(比如一个固定的字符串或数字 1),可以使用 lambda 表达式。
from collections import defaultdict
# 用例1:默认值是一个固定字符串 "N/A"
d_str = defaultdict(lambda: "N/A")
print(f"访问 'key1': {d_str['key1']}")
print(f"字典内容: {dict(d_str)}")
# 用例2:默认值是 100 (而不是 0)
d_num = defaultdict(lambda: 100)
d_num['item_a'] += 5 # 第一次访问,100 + 5
print(f"访问 'item_a': {d_num['item_a']}")
输出:
访问 'key1': N/A
字典内容: {'key1': 'N/A'}
访问 'item_a': 105
总结
defaultdict 是 Python collections 库中的一颗明珠。
-
何时使用? 当你需要计数、分组或聚合数据时,它几乎总是比常规
dict更简洁、更易读。 -
如何使用?
-
计数:
defaultdict(int)(默认值0) -
分组:
defaultdict(list)(默认值[]) -
收集唯一项:
defaultdict(set)(默认值set()) -
自定义默认值:
defaultdict(lambda: "some_value")
-
提醒: defaultdict 只是在键不存在时表现不同。一旦键被创建,它的所有行为(赋值、删除)都和普通 dict 一模一样。
更多推荐


所有评论(0)