数据科学的秘密武器:defaultdict——Python字典的自动化填充神器,让数据结构更灵活
目录
什么是defaultdict
引入动机
创建与初始化
工作原理
自定义默认值函数
注意事项
使用案例
使用场景 1: 计数
使用场景 2: 分组数据
使用场景 3: 嵌套字典结构
进阶案例使用
进阶案例 1: 使用 defaultdict 实现词频统计并排序
进阶案例 2: 使用 defaultdict 实现多层嵌套的字典结构
什么是defaultdict
defaultdict
是 Python 标准库collections
模块中的一个非常有用的类,它提供了一个带有默认值的字典(dictionary)。与普通字典相比,defaultdict
的特别之处在于它允许你指定一个函数,该函数会在字典中访问的键不存在时自动被调用,从而提供一个默认值。这个特性使得在处理数据时,特别是在处理可能缺少某些键的数据时,代码更加简洁和高效。
引入动机
普通字典(
dict
)在访问不存在的键时会引发KeyError
。为了处理这种情况,通常需要编写额外的代码来检查键是否存在,或者在使用键之前先给字典设置一个默认值。而defaultdict
通过提供一个默认的值或值的生成函数,自动处理了这种情况,使得代码更加简洁和易于编写。
创建与初始化
defaultdict
通过其构造函数接受一个函数作为参数来创建。这个函数不接受任何参数,并返回一个值,该值将用作字典中不存在的键的默认值。常见的用法包括使用内置的工厂函数(如 int
、list
、set
)来创建不同类型的默认值。
from collections import defaultdict
# 使用 list 作为默认值
dd_list = defaultdict(list)
# 使用 int 作为默认值
dd_int = defaultdict(int)
# 使用 set 作为默认值
dd_set = defaultdict(set)
工作原理
当尝试访问 defaultdict
中不存在的键时,defaultdict
会自动调用其构造函数中指定的函数来生成一个默认值,并将该值与键一起存储在字典中。这意味着,每次访问不存在的键时,都会得到一个新的默认值实例(对于像 list
或 set
这样的可变类型),而对于像 int
这样的不可变类型,则始终返回同一个默认值(但可以通过赋值操作来修改它)。
dd_list['a'].append(1) # 'a' 不存在,调用 list() 创建一个列表并附加 1
print(dd_list) # 输出: defaultdict(<class 'list'>, {'a': [1]})
dd_int['x'] += 1 # 'x' 不存在,调用 int() 创建一个整数 0,然后加 1
print(dd_int) # 输出: defaultdict(<class 'int'>, {'x': 1})
自定义默认值函数
除了使用内置的工厂函数外,还可以传递自定义函数作为 defaultdict
的默认值生成器。这允许创建更复杂的默认值结构。
def my_default():
return {'count': 0, 'data': []}
dd_custom = defaultdict(my_default)
dd_custom['key1']['count'] += 1
dd_custom['key1']['data'].append('item')
print(dd_custom) # 输出包含自定义结构的 defaultdict
注意事项
- 默认值函数不应接受任何参数,并返回一个值作为默认值。
- 对于像
list
、set
等可变类型,每次访问不存在的键时都会创建一个新的实例。因此,如果需要在多个键之间共享状态,请小心使用。 defaultdict
提供了与普通字典相同的接口,包括update()
、keys()
、values()
、items()
等方法。
使用案例
使用场景 1: 计数
当需要对一组元素进行计数时,defaultdict
可以非常方便地避免检查键是否存在的麻烦。
案例代码:
from collections import defaultdict
# 创建一个defaultdict,其默认值为int类型的0
counts = defaultdict(int)
# 假设我们有一个单词列表
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
# 对每个单词进行计数
for word in words:
counts[word] += 1 # 如果word不在字典中,则默认值为0,然后加1
# 打印结果
for word, count in counts.items():
print(f"{word}: {count}")
# 运行结果
# apple: 3
# banana: 2
# cherry: 1
# 代码描述: 使用defaultdict对单词列表中的每个单词进行计数,无需显式检查键是否存在。
使用场景 2: 分组数据
当你需要将数据分组到不同的子结构中时(如列表或集合),defaultdict
可以帮助你快速初始化这些子结构。
案例代码:
from collections import defaultdict
# 创建一个defaultdict,其默认值为空列表
groups = defaultdict(list)
# 假设我们有一组学生及其所属班级
students = [('Alice', 'A'), ('Bob', 'B'), ('Charlie', 'A'), ('David', 'C'), ('Eve', 'B')]
# 根据班级将学生分组
for student, class_ in students:
groups[class_].append(student)
# 打印结果
for class_, students_in_class in groups.items():
print(f"Class {class_}: {students_in_class}")
# 运行结果
# Class A: ['Alice', 'Charlie']
# Class B: ['Bob', 'Eve']
# Class C: ['David']
# 代码描述: 使用defaultdict将学生按班级分组到不同的列表中。
使用场景 3: 嵌套字典结构
当你需要处理嵌套的字典结构,并且想要自动初始化缺失的嵌套级别时,defaultdict
可以非常有用。
案例代码:
from collections import defaultdict
# 创建一个defaultdict,其默认值为另一个defaultdict,后者的默认值为int类型的0
nested_dict = defaultdict(lambda: defaultdict(int))
# 假设我们要记录一些人的年龄和他们的身高(单位:厘米)
data = [
('Alice', 'age', 30),
('Alice', 'height', 165),
('Bob', 'age', 25),
('Bob', 'height', 175),
('Charlie', 'age', 35),
]
# 填充嵌套字典
for name, attribute, value in data:
nested_dict[name][attribute] = value
# 打印结果
for name, attributes in nested_dict.items():
print(f"{name}: {attributes}")
# 运行结果
# Alice: defaultdict(<class 'int'>, {'age': 30, 'height': 165})
# Bob: defaultdict(<class 'int'>, {'age': 25, 'height': 175})
# Charlie: defaultdict(<class 'int'>, {'age': 35})
# 注意:实际输出中defaultdict的显示可能略有不同,但核心结构是正确的。
# 代码描述: 使用嵌套的defaultdict来记录人的不同属性(如年龄和身高),自动初始化缺失的嵌套级别。
进阶案例使用
进阶案例 1: 使用 defaultdict
实现词频统计并排序
案例代码:
from collections import defaultdict
# 假设我们有一段文本
text = "this is a sample text to demonstrate the use of defaultdict for word frequency analysis"
# 使用空格分割文本成单词列表,并转换为小写以忽略大小写差异
words = text.lower().split()
# 创建一个defaultdict来统计词频,默认值为int类型的0
word_counts = defaultdict(int)
# 统计每个单词的出现次数
for word in words:
# 过滤掉标点符号等非单词字符(这里简化处理,只移除单引号)
word = word.replace("'", "")
# 忽略空字符串
if word:
word_counts[word] += 1
# 将词频统计结果转换为列表,以便排序
sorted_word_counts = sorted(word_counts.items(), key=lambda x: x[1], reverse=True)
# 打印结果
for word, count in sorted_word_counts:
print(f"{word}: {count}")
# 运行结果(注意:由于文本较短且随机,结果可能与示例不同)
# to: 2
# of: 1
# use: 1
# text: 1
# this: 1
# the: 1
# sample: 1
# a: 1
# demonstrate: 1
# is: 1
# defaultdict: 1
# for: 1
# word: 1
# analysis: 1
# frequency: 1
# 代码描述: 使用defaultdict对一段文本进行词频统计,并根据词频进行降序排序。
进阶案例 2: 使用 defaultdict
实现多层嵌套的字典结构
案例代码:
from collections import defaultdict
# 创建一个defaultdict,其默认值为另一个defaultdict,后者再默认为另一个defaultdict,以支持多层嵌套
nested_dict = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
# 假设我们要记录不同年份、不同月份、不同日期的天气情况(晴、阴、雨)
data = [
('2023', '01', '01', '晴'),
('2023', '01', '02', '阴'),
('2023', '01', '03', '雨'),
('2023', '02', '01', '晴'),
('2022', '12', '31', '晴'),
]
# 填充多层嵌套字典
for year, month, day, weather in data:
nested_dict[year][month][day][weather] += 1
# 打印结果(部分)
for year, months in nested_dict.items():
print(f"Year: {year}")
for month, days in months.items():
print(f" Month: {month}")
for day, weather_counts in days.items():
print(f" Day: {day}, {weather_counts}")
# 注意:由于实际输出会非常长且包含嵌套结构,这里只展示了部分输出结构
# 运行结果(简化)
# Year: 2023
# Month: 01
# Day: 01, defaultdict(<class 'int'>, {'晴': 1})
# Day: 02, defaultdict(<class 'int'>, {'阴': 1})
# Day: 03, defaultdict(<class 'int'>, {'雨': 1})
# Month: 02
# Day: 01, defaultdict(<class 'int'>, {'晴': 1})
# Year: 2022
# Month: 12
# Day: 31, defaultdict(<class 'int'>, {'晴': 1})
# 注意:为了简化输出,这里省略了嵌套的 defaultdict 显示,实际输出中会显示 defaultdict。
# 代码描述: 使用多层嵌套的 defaultdict 来记录不同年份、月份、日期的天气情况,并统计每种天气的出现次数。