字典推导式(Dictionary Comprehension)是 Python 中用于生成字典的一种简洁语法,它可以把遍历、条件筛选、键值转换和结果收集写在一个表达式中,适合根据已有数据快速建立键与值之间的对应关系。
字典推导式与列表推导式和集合推导式的基本结构相似,但输出表达式由键和值两部分组成,生成的结果是字典。
一、什么是字典推导式
1、基本语法
字典推导式使用 { } 生成字典,基本形式为:
{键表达式: 值表达式 for 变量 in 可迭代对象}
例如,生成数字及其平方之间的对应关系:
squares = { x: x ** 2 for x in range(5)}print(squares)
输出:
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
这段字典推导式可以分成三个部分:
x: x ** 2 # 生成当前键和值for x # 将当前元素依次绑定给 xin range(5) # 指定要遍历的数据
使用普通循环可以写成:
squares = {}for x in range(5): squares[x] = x ** 2print(squares)
普通循环通过赋值语句向字典中写入键值对,字典推导式则将遍历、计算和收集写在一个表达式中。
2、字典推导式的结果特点
(1)字典由键值对组成
列表推导式和集合推导式只需要一个输出表达式,字典推导式则必须同时生成键和值。在输出表达式中,冒号左侧生成键,右侧生成对应的值。
键和值都可以根据当前元素进行计算:
mapping = { f"item_{x}": x * 10 for x in range(3)}print(mapping)
输出:
{'item_0': 0, 'item_1': 10, 'item_2': 20}
(2)字典中的键不能重复
字典中的每个键都是唯一的。如果不同元素生成了相同的键,后生成的值会覆盖该键原来的值。
pairs = [ ("name", "张三"), ("city", "长沙"), ("name", "李四")]result = { key: value for key, value in pairs}print(result)
输出:
{'name': '李四', 'city': '长沙'}
键 "name" 出现了两次,后面的 "李四" 覆盖了前面的 "张三"。
集合会合并重复元素;字典遇到重复键时,则会用后生成的值覆盖该键原来的值。
3、字典键必须可哈希
字典通过哈希机制查找和管理键,因此字典的键必须是可哈希对象(Hashable Object)。
数值、字符串以及由可哈希元素组成的元组,都可以作为字典键:
points = { (x, x ** 2): x for x in range(4)}print(points)
输出可能为:
{(0, 0): 0, (1, 1): 1, (2, 4): 2, (3, 9): 3}
列表、字典和普通集合不能作为字典键。例如:
mapping = { [x, x ** 2]: x for x in range(4)}
运行后会引发 TypeError,因为列表不可哈希。
字典的值没有这一限制,可以是数值、字符串、列表、字典或其他对象。
二、带条件的字典推导式
字典推导式中的条件主要有两种用途:
• 条件筛选:决定当前遍历到的元素是否参与键值对生成
• 条件表达式:根据条件选择当前要生成的键或值
两种写法中,if 所在的位置不同。
1、条件筛选
在 for 后面添加 if,可以只处理满足条件的元素。
基本语法为:
{
键表达式: 值表达式
for 变量 in 可迭代对象
if 条件
}
例如,只保留偶数及其平方:
even_squares = { x: x ** 2 for x in range(10) if x % 2 == 0}print(even_squares)
输出:
{0: 0, 2: 4, 4: 16, 6: 36, 8: 64}
只有满足 x % 2 == 0 的数字才会参与键值对生成。
2、条件表达式
条件表达式可以用在键表达式或值表达式中,根据条件选择不同结果。
例如,根据成绩生成是否及格的状态:
scores = { "张三": 86, "李四": 58, "王五": 73}status = { name: "pass" if score >= 60 else "fail" for name, score in scores.items()}print(status)
输出:
{'张三': 'pass', '李四': 'fail', '王五': 'pass'}
这里的键仍然使用学生姓名,值则由条件表达式决定。
条件表达式也可以用于生成键,但需要注意:如果不同元素生成相同的键,后面的值会覆盖前面的值。
三、包含多个 for 子句的字典推导式
字典推导式中可以连续使用多个 for 子句,表示多层循环。
基本语法为:
{
键表达式: 值表达式
for 变量1 in 可迭代对象1
for 变量2 in 可迭代对象2
}
多个 for 子句从左到右,对应普通循环由外到内的顺序。
例如,以坐标元组为键,保存对应坐标的乘积:
products = { (x, y): x * y for x in range(1, 3) for y in range(1, 4)}print(products)
输出:
{ (1, 1): 1, (1, 2): 2, (1, 3): 3, (2, 1): 2, (2, 2): 4, (2, 3): 6}
在这段代码中:
for x 是外层循环;
for y 是内层循环;
(x, y) 是字典键;
x * y 是对应的值。
多个 for 子句后面也可以添加筛选条件。每组满足条件的变量取值都会用于计算键表达式和值表达式,再生成一个键值对。
四、字典推导式的典型应用场景
字典推导式适合建立索引、转换现有字典、组合对应数据,以及生成模型所需的编号映射。
1、筛选并转换现有字典
遍历已有字典时,可以使用 items() 同时取得键和值。
例如,只保留价格不低于 10 的商品,并计算九折价格:
prices = { "apple": 8.0, "banana": 12.0, "orange": 15.0, "pear": 6.0}discounted_prices = { name: round(price * 0.9, 2) for name, price in prices.items() if price >= 10}print(discounted_prices)
输出:
{'banana': 10.8, 'orange': 13.5}
这个字典推导式同时完成了条件筛选和值转换。
2、将记录转换为查询索引
实际数据经常由多条字典记录组成。可以选择其中具有唯一性的字段作为键,建立便于查询的索引。
users = [ {"id": 101, "name": "张三", "city": "长沙"}, {"id": 102, "name": "李四", "city": "北京"}, {"id": 103, "name": "王五", "city": "上海"}]users_by_id = { user["id"]: user for user in users}print(users_by_id[102])
输出:
{'id': 102, 'name': '李四', 'city': '北京'}
转换后,可以直接通过用户编号查找对应记录,不必每次遍历整个列表。
作为键的字段应具有唯一性。若多条记录使用同一个编号,后面的记录会覆盖前面的记录。
3、组合位置对应的数据
zip() 可以同时遍历多组位置对应的数据。与字典推导式配合,可以建立字段与数据之间的对应关系,并在生成过程中进行转换。
fields = ["name", "email", "city"]values = [" 张三 ", "ZHANG3@EXAMPLE.COM", " 长沙 "]record = { field: value.strip() for field, value in zip(fields, values)}print(record)
输出:
{'name': '张三', 'email': 'ZHANG3@EXAMPLE.COM', 'city': '长沙'}
zip() 建立字段和值之间的对应关系,字典推导式则对值进行清洗并生成字典。
当多个可迭代对象长度不同时,zip() 会在最短的对象结束时停止。
4、为 AI 词元建立编号映射
在自然语言处理任务中,模型通常需要将词元(Token)映射为整数编号。
可以使用 enumerate() 与字典推导式建立词元到编号的映射:
tokens = [ "<PAD>", "<UNK>", "python", "ai", "model", "data"]token_to_id = { token: index for index, token in enumerate(tokens)}print(token_to_id)
输出:
{ '<PAD>': 0, '<UNK>': 1, 'python': 2, 'ai': 3, 'model': 4, 'data': 5}
enumerate() 提供连续编号,字典推导式则建立词元到编号的查询关系。
词元列表还应避免包含重复词元。
此外,编号顺序应保持稳定。如果词元来自集合,应先排序或按照明确规则组织成列表,再分配编号,避免不同运行中产生不一致的映射。
五、常见问题及使用建议
1、无须转换时直接使用 dict()
如果数据已经由键值对组成,并且不需要筛选或转换,可以直接使用 dict():
keys = ["name", "city"]values = ["张三", "长沙"]record = dict(zip(keys, values))
下面的字典推导式虽然也能得到相同结果,但没有必要:
record = { key: value for key, value in zip(keys, values)}
需要计算键、转换值或添加筛选条件时,字典推导式更合适。
2、注意重复键覆盖
字典推导式生成键时,应确保键表达式符合预期。
例如,将字符串统一转换为小写后,原本不同的字符串可能生成相同的键:
names = ["Alice", "ALICE", "Bob"]mapping = { name.lower(): name for name in names}print(mapping)
输出:
{'alice': 'ALICE', 'bob': 'Bob'}
"Alice" 和 "ALICE" 都生成键 "alice",因此后面的值覆盖了前面的值。
3、不适合直接完成计数或分组
字典推导式遇到重复键时只会覆盖已有值,不会自动累加或合并数据。
因此,下面的写法不能正确统计字符出现次数:
text = "banana"counts = { character: 1 for character in text}
结果只表示出现过哪些字符,每个值都是 1。
需要统计频次时,可以使用 collections.Counter:
from collections import Countercounts = Counter(text)
需要将多条数据按键分组时,通常应使用普通循环、defaultdict 或其他专门方法。
4、避免堆叠过多逻辑
字典推导式适合结构简单、目的明确的字典生成任务。
当键和值都包含复杂计算,或者推导式中包含多个循环、多个条件和异常处理时,普通循环通常更容易阅读、调试和维护。
5、避免副作用操作
字典推导式的主要目的是生成字典,与其他推导式一样,不适合只用于输出、写入文件或修改外部状态。
📘 小结
字典推导式可以在遍历过程中生成键值对,并完成键值转换和条件筛选。它适合建立查询索引、转换现有字典、组合对应数据和构建编号映射。使用时应注意字典键必须可哈希,重复键会覆盖原有值,计数和分组任务通常应使用其他方法。