Python基础指南之字典推导式与字典合并技巧
一、开篇:让字典操作更优雅
前面我们学完了字典的创建、CRUD和遍历。今天我们要攻克两个让字典操作效率大幅提升的高级技巧:字典推导式和字典合并。
字典推导式之于字典,就像列表推导式之于列表。它能让你用一行代码完成"过滤+转换+构建新字典"的完整操作。而字典合并则是处理多来源配置、覆盖默认值、聚合数据时不可或缺的能力。
这两个技巧在实际开发中使用频率极高。掌握了它们,你处理字典数据的代码量通常会减少一半以上——更重要的是,代码的意图表达会更清晰。
二、字典推导式基础
2.1 基本语法
# 语法:{key_expr: value_expr for item in iterable}
# 最简单的推导式
squares = {x: x ** 2 for x in range(6)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# 等价于:
squares = {}
for x in range(6):
squares[x] = x ** 2
# 从两个列表构建字典
keys = ['name', 'age', 'city']
values = ['小明', 25, '北京']
person = {k: v for k, v in zip(keys, values)}
print(person) # {'name': '小明', 'age': 25, 'city': '北京'}
2.2 带条件的字典推导式
# 过滤——只保留满足条件的项
numbers = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
# 只保留值大于2的
filtered = {k: v for k, v in numbers.items() if v > 2}
print(filtered) # {'c': 3, 'd': 4, 'e': 5}
# 只保留键以特定前缀开头的
config = {
'DB_HOST': 'localhost',
'DB_PORT': 5432,
'CACHE_HOST': 'redis://localhost',
'CACHE_PORT': 6379,
'APP_NAME': 'MyApp',
}
# 提取所有数据库相关配置
db_config = {k: v for k, v in config.items() if k.startswith('DB_')}
print(db_config) # {'DB_HOST': 'localhost', 'DB_PORT': 5432}
# 提取缓存相关配置并去掉前缀
cache_config = {
k.replace('CACHE_', '').lower(): v
for k, v in config.items()
if k.startswith('CACHE_')
}
print(cache_config) # {'host': 'redis://localhost', 'port': 6379}
2.3 键和值的变换
# 变换键
original = {'a': 1, 'b': 2, 'c': 3}
uppercase_keys = {k.upper(): v for k, v in original.items()}
print(uppercase_keys) # {'A': 1, 'B': 2, 'C': 3}
# 变换值
prices = {'apple': 10, 'banana': 5, 'orange': 8}
with_tax = {k: v * 1.1 for k, v in prices.items()}
print(with_tax) # {'apple': 11.0, 'banana': 5.5, 'orange': 8.8}
# 同时变换键和值
original = {'user_name': 'xiaoming', 'user_email': 'xm@test.com'}
cleaned = {
k.replace('user_', '').upper(): v.upper()
for k, v in original.items()
}
print(cleaned) # {'NAME': 'XIAOMING', 'EMAIL': 'XM@TEST.COM'}
# 反转字典(键值互换)
original = {'a': 1, 'b': 2, 'c': 3}
reversed_dict = {v: k for k, v in original.items()}
print(reversed_dict) # {1: 'a', 2: 'b', 3: 'c'}
# ⚠️ 注意:如果值有重复,后面的会覆盖前面的
original = {'a': 1, 'b': 2, 'c': 1}
reversed_dict = {v: k for k, v in original.items()}
print(reversed_dict) # {1: 'c', 2: 'b'}('a'被'c'覆盖了)
# 处理值重复的反转
def safe_reverse(d):
"""安全反转——值重复时收集所有键"""
result = {}
for k, v in d.items():
result.setdefault(v, []).append(k)
return result
print(safe_reverse({'a': 1, 'b': 2, 'c': 1})) # {1: ['a', 'c'], 2: ['b']}
三、字典推导式的高级用法
3.1 嵌套推导式
# 从嵌套数据构建字典
# 例:从元组列表构建嵌套字典
students = [
('小明', {'math': 85, 'english': 90}),
('小红', {'math': 92, 'english': 88}),
('小刚', {'math': 78, 'english': 85}),
]
# 找出数学成绩大于80的学生
math_above_80 = {
name: scores
for name, scores in students
if scores['math'] > 80
}
print(math_above_80)
# {'小明': {'math': 85, 'english': 90}, '小红': {'math': 92, 'english': 88}}
# 从查询结果构建嵌套字典
products = [
('电子产品', '手机', 2999),
('电子产品', '电脑', 5999),
('食品', '苹果', 5),
('食品', '香蕉', 3),
('服装', 'T恤', 99),
]
# 构建 {类别: {商品: 价格}}
catalog = {}
for category, item, price in products:
catalog.setdefault(category, {})[item] = price
print(catalog)
# {'电子产品': {'手机': 2999, '电脑': 5999}, ...}
# 更简洁:用字典推导式分组
from collections import defaultdict
catalog2 = defaultdict(dict)
for category, item, price in products:
catalog2[category][item] = price
print(dict(catalog2))
3.2 条件表达式在推导式中的应用
scores = {'小明': 85, '小红': 92, '小刚': 78, '小李': 95, '小王': 60}
# if-else在键或值的表达式中
# 给及格和不及格的人不同标注
graded = {
k: ('优秀' if v >= 90 else '良好' if v >= 80 else '及格' if v >= 60 else '不及格')
for k, v in scores.items()
}
print(graded)
# {'小明': '良好', '小红': '优秀', '小刚': '良好', '小李': '优秀', '小王': '及格'}
# 键和值都加条件变换
transformed = {
k.upper() if v >= 90 else k.lower(): v * 2 if v < 80 else v
for k, v in scores.items()
}
print(transformed)
# 只对满足条件的键值对做变换(if在for后面=过滤)
high_scores = {
k: v for k, v in scores.items() if v >= 80
}
print(high_scores) # {'小明': 85, '小红': 92, '小李': 95}
3.3 从列表推导式到字典推导式
# 场景:统计词频
text = 'hello world hello python world hello'
# 方式一:普通循环
word_count = {}
for word in text.split():
word_count[word] = word_count.get(word, 0) + 1
# 方式二:使用Counter(最简单)
from collections import Counter
word_count = dict(Counter(text.split()))
# 方式三:字典推导式 + set
unique_words = set(text.split())
word_count = {word: text.split().count(word) for word in unique_words}
# 注意:这种方式效率低(每个词都要count一遍全文)
# 场景:构建索引
words = ['apple', 'banana', 'cherry', 'date', 'elderberry']
# {首字母: [单词列表]}
index = {ch: [w for w in words if w.startswith(ch)]
for ch in set(w[0] for w in words)}
print(index)
# {'a': ['apple'], 'b': ['banana'], 'c': ['cherry'], 'd': ['date'], 'e': ['elderberry']}
四、字典合并的四种方式
4.1 | 运算符(Python 3.9+,最推荐)
# 合并两个字典,返回新字典
defaults = {'host': 'localhost', 'port': 8080, 'debug': False}
overrides = {'port': 9090, 'debug': True, 'timeout': 30}
config = defaults | overrides
print(config)
# {'host': 'localhost', 'port': 9090, 'debug': True, 'timeout': 30}
# 有重复键时,右边的值覆盖左边的
# 链式合并
d1 = {'a': 1}
d2 = {'b': 2}
d3 = {'c': 3}
merged = d1 | d2 | d3
print(merged) # {'a': 1, 'b': 2, 'c': 3}
# 注意:| 运算符要求两边都是dict
# {1: 'a'} | [(2, 'b')] # TypeError
4.2 |= 原地合并(Python 3.9+)
config = {'host': 'localhost', 'port': 8080}
# |= 原地修改现有字典
config |= {'debug': True, 'port': 9090}
print(config)
# {'host': 'localhost', 'port': 9090, 'debug': True}
# 等价于 config.update({'debug': True, 'port': 9090})
# 可以接受任何可迭代的键值对
config |= [('timeout', 30), ('retries', 3)]
print(config)
# {'host': 'localhost', 'port': 9090, 'debug': True, 'timeout': 30, 'retries': 3}
4.3 ** 解包(Python 3.5+)
d1 = {'a': 1, 'b': 2}
d2 = {'c': 3, 'd': 4}
d3 = {'a': 100, 'e': 5}
# 用 ** 解包合并
merged = {**d1, **d2, **d3}
print(merged) # {'a': 100, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
# 同样的覆盖规则:后面的覆盖前面的
# d3的'a':100覆盖了d1的'a':1
# 在合并时插入额外的键
merged = {**d1, 'extra': 'value', **d2}
print(merged) # {'a': 1, 'b': 2, 'extra': 'value', 'c': 3, 'd': 4}
# ** 解包的一个限制:键必须是字符串
# d = {**{1: 'one'}, **{2: 'two'}} # TypeError(Python限制)
# 这种情况下用 | 或 update()
4.4 update()(Python 3.0+)
d = {'a': 1, 'b': 2}
# update() 原地合并,接受多种参数形式
d.update({'c': 3}) # 字典
d.update(d=4, e=5) # 关键字参数
d.update([('f', 6)]) # 键值对可迭代对象
print(d) # {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
# update() 可以和 | 对比
d1 = {'a': 1, 'b': 2}
d2 = {'b': 20, 'c': 3}
# 方式A:合并+创建新字典(|)
d3 = d1 | d2
# d1不变,d3是新的
# 方式B:合并+原地修改(update)
d1.update(d2)
# d1被修改了
# 选择:需要保留原字典用 |,不需要保留用 update
五、合并方式的对比与选择
方式对比表
| 方式 | Python版本 | 创建新字典 | 原地修改 | 要求键是字符串 |
|---|---|---|---|---|
| 运算符 | 3.9+ | 是 | 否 | 否(任意可哈希类型) |
|= 原地合并 | 3.9+ | 否 | 是 | 否(任意可哈希类型) |
** 解包 | 3.5+ | 是 | 否 | 是(仅限字符串键) |
update() | 全部 | 否 | 是 | 多种参数形式 |
选择建议:
- Python 3.9+、需要新字典 → d1 | d2
- Python 3.9+、原地修改 → d1 |= d2
- Python 3.5-3.8、需要新字典 → {**d1, **d2}
- 任何Python版本、原地修改 → d1.update(d2)
- 需要兼容旧代码 → update()始终可用
六、实战:配置管理系统
class Config:
"""多层级配置管理系统"""
def __init__(self, defaults=None):
self._config = defaults.copy() if defaults else {}
def load(self, **overrides):
"""加载配置——合并并覆盖"""
# 过滤掉None值
valid_overrides = {k: v for k, v in overrides.items() if v is not None}
self._config |= valid_overrides
def load_from_file(self, file_config):
"""从文件加载配置"""
# 只加载已知的配置项
known_keys = self._config.keys()
filtered = {k: v for k, v in file_config.items() if k in known_keys}
self._config |= filtered
def get(self, key, default=None):
return self._config.get(key, default)
def to_dict(self):
return self._config.copy()
def clone(self):
"""创建配置副本(用于子组件)"""
return Config(self._config.copy())
# 使用示例
default_config = {
'host': 'localhost',
'port': 8080,
'debug': False,
'log_level': 'INFO',
'max_connections': 100,
'timeout': 30,
}
app_config = Config(default_config)
# 加载环境特定配置
app_config.load(port=9090, debug=True)
# 加载文件配置
file_config = {
'host': 'prod.server.com',
'port': 443,
'debug': False,
'log_level': 'WARNING',
'unknown_key': 'should_be_ignored',
}
app_config.load_from_file(file_config)
print('最终配置:')
for k, v in app_config.to_dict().items():
print(f' {k}: {v}')
七、字典合并的经典模式
# 模式一:默认值 + 覆盖
def create_user(**kwargs):
defaults = {'role': 'user', 'active': True, 'level': 1}
return defaults | kwargs # kwargs覆盖defaults
print(create_user(name='小明', role='admin'))
# {'role': 'admin', 'active': True, 'level': 1, 'name': '小明'}
# 模式二:层级配置合并
def merge_deep(base, override):
"""深度合并两个字典(嵌套字典递归合并)"""
result = base.copy()
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = merge_deep(result[key], value)
else:
result[key] = value
return result
base = {
'database': {'host': 'localhost', 'port': 5432, 'pool': {'min': 1}},
'cache': {'host': 'localhost', 'port': 6379},
}
override = {
'database': {'port': 5433, 'pool': {'max': 20}},
}
merged = merge_deep(base, override)
print(merged)
# {'database': {'host': 'localhost', 'port': 5433, 'pool': {'min': 1, 'max': 20}},
# 'cache': {'host': 'localhost', 'port': 6379}}
# 模式三:多源数据聚合
def aggregate_data(*sources):
"""聚合多个数据源——后面的覆盖前面的"""
result = {}
for source in sources:
if source:
result |= source
return result
user_from_db = {'id': 1, 'name': '小明', 'email': 'xm@test.com'}
user_from_api = {'name': '小明(更新)', 'phone': '13800138000'}
user_from_cache = {'last_login': '2025-06-01'}
final_user = aggregate_data(user_from_db, user_from_api, user_from_cache)
print(final_user)
八、常见陷阱和注意事项
# 陷阱一:| 运算符的优先级
# d = {'a': 1} | {'b': 2} if condition else {}
# 上面的代码可能不是你期望的意思
# 建议加括号
# d = ({'a': 1} | {'b': 2}) if condition else {}
# 陷阱二:** 解包不能处理非字符串键
# d = {**{1: 'a'}, **{2: 'b'}} # TypeError
# 用 | 或 update 代替
d = {1: 'a'} | {2: 'b'}
print(d) # {1: 'a', 2: 'b'}
# 陷阱三:推导式中的变量作用域
# 字典推导式有自己的局部作用域(Python 3+)
# 不会污染外部命名空间
x = 'outer'
d = {x: x * 3 for x in 'abc'}
print(d) # {'a': 'aaa', 'b': 'bbb', 'c': 'ccc'}
print(x) # 'outer'(外部变量不受影响)
# 陷阱四:空字典的处理
empty = {}
# empty | {} # 这是OK的
# 但注意:None | {} 会报TypeError
config = None
# merged = config | {} # TypeError(如果config是None)
# 正确:先检查
merged = (config or {}) | {}
九、本篇小结
字典推导式和合并操作是Python字典的两大高级利器:
字典推导式:
- 语法:
{key_expr: value_expr for item in iterable if condition} - 适合:过滤、键值转换、反转、构建新字典
- 比等价的for循环更短、更快、更易读
字典合并(四种方式):
d1 | d2(Python 3.9+)—— 创建新字典,最推荐d1 |= d2(Python 3.9+)—— 原地修改{**d1, **d2}(Python 3.5+)—— 创建新字典,仅限字符串键d1.update(d2)(所有版本)—— 原地修改,最通用
字典推导式和字典合并是日常开发中的高频操作。熟练掌握它们,你处理字典数据的代码会更简洁、更Pythonic。下一篇我们学习collections模块中的defaultdict和OrderedDict——两个在特定场景下比普通字典更好用的字典变体。
以上就是Python基础指南之字典推导式与字典合并技巧的详细内容,更多关于Python字典推导式与合并的资料请关注脚本之家其它相关文章!
相关文章
Pycharm中配置使用Anaconda的虚拟环境进行项目开发的图文教程
今天在一台电脑上跑环境的时候,发现已经装了Pytorch了,但是运行没有用,提示报错:OSError: [WinError 126] 找不到指定的模块,但其实cmd进入虚拟环境是可以调用torch的,故本文给大家介绍了Pycharm中配置使用Anaconda的虚拟环境进行项目开发的图文教程2024-09-09


最新评论