学习笔记:用 {k: v for ...} 一行生成字典——语法、等价写法、过滤条件,以及在本项目里的真实用法。


前言

在 Python 里,除了用 for 循环一个个 dict[key] = value 填字典,还可以用 字典推导式(dict comprehension) 更简洁地生成 dict。

你在项目 src/config/configuration.py 里已经见过:

values: dict[str, Any] = {
    f.name: os.environ.get(f.name.upper(), configurable.get(f.name))
    for f in fields(cls)
    if f.init
}

return cls(**{k: v for k, v in values.items() if v})

本文从基础语法讲起,再讲到这两行代码在干什么。


1. 什么是字典推导式?

基本形式:

{键表达式: 值表达式 for 变量 in 可迭代对象}

带过滤条件:

{键表达式: 值表达式 for 变量 in 可迭代对象 if 条件}

类比:

推导式 生成结果
列表推导式 [x for x in ...] list
字典推导式 {k: v for ...} dict
集合推导式 {x for ...} set

2. 最简单的例子

2.1 从列表生成 dict

nums = [1, 2, 3, 4, 5]

squares = {n: n * n for n in nums}
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}

读法:对每个 n,键是 n,值是 n * n

2.2 等价于普通 for 循环

squares = {}
for n in nums:
    squares[n] = n * n

字典推导式就是把这种「循环 + 赋值」压缩成一行。


3. 带 if 过滤

只保留满足条件的项:

nums = [1, 2, 3, 4, 5, 6]

evens = {n: n * n for n in nums if n % 2 == 0}
# {2: 4, 4: 16, 6: 36}

等价于:

evens = {}
for n in nums:
    if n % 2 == 0:
        evens[n] = n * n

注意: 这里的 if 写在 for 后面,表示「过滤」——不满足条件的根本不会进 dict。


4. 键和值可以是任意表达式

4.1 键、值都可以变换

words = ["apple", "banana", "cat"]

length_map = {word: len(word) for word in words}
# {'apple': 5, 'banana': 6, 'cat': 3}

upper_key = {word.upper(): word for word in words}
# {'APPLE': 'apple', 'BANANA': 'banana', 'CAT': 'cat'}

4.2 从两个列表「拉链」成 dict(常见技巧)

keys = ["name", "age", "city"]
values = ["Alice", 20, "Beijing"]

person = {k: v for k, v in zip(keys, values)}
# {'name': 'Alice', 'age': 20, 'city': 'Beijing'}

Python 3 里更常见的写法是直接 dict(zip(keys, values)),但推导式同样清晰。


5. 遍历 dict:.items() + 推导式

对已存在的 dict 做「变换」或「过滤」,常用 .items()

values = {
    "max_step_num": 5,
    "agent_type": None,
    "mcp_settings": {"servers": {}},
}

filtered = {k: v for k, v in values.items() if v}
# {'max_step_num': 5, 'mcp_settings': {'servers': {}}}

拆解 .items()

values.items()
# dict_items([
#   ('max_step_num', 5),
#   ('agent_type', None),
#   ('mcp_settings', {'servers': {}}),
# ])

每次循环得到一对 (k, v)

for k, v in values.items():
    print(k, v)
# max_step_num 5
# agent_type None
# mcp_settings {'servers': {}}

if v 是什么意思?

在 Python 里,if v 判断 v 是否为「真值」(truthy)

v if v 是否保留
5
"hello"
{"a": 1}
None
""
0
[]
False

所以 {k: v for k, v in values.items() if v} 的含义是:

只保留值不为空/不为 None/不为 0 的键值对,生成一个新 dict。

等价写法:

filtered = {}
for k, v in values.items():
    if v:
        filtered[k] = v

6. 和 ** 一起用:解包成关键字参数

过滤后的 dict 常配合 ** 传给函数或类:

filtered = {"max_step_num": 5}

cls(**filtered)
# 等价于 cls(max_step_num=5)

完整链条:

return cls(**{k: v for k, v in values.items() if v})
  1. {k: v for k, v in values.items() if v} → 得到过滤后的 dict
  2. **{...} → 展开成 cls(字段名=值, ...)
  3. 没出现在 dict 里的字段 → 使用 dataclass 默认值

7. 项目里的真实例子

例子 1:从 dataclass 字段生成配置 dict

values: dict[str, Any] = {
    f.name: os.environ.get(f.name.upper(), configurable.get(f.name))
    for f in fields(cls)
    if f.init
}
部分 含义
for f in fields(cls) 遍历 Configuration 每个 dataclass 字段
if f.init 只处理会参与构造的字段
f.name 字段名,如 "max_step_num"
f.name.upper() 环境变量名,如 "MAX_STEP_NUM"
os.environ.get(..., configurable.get(f.name)) 优先环境变量,否则用请求 config

假设 Configuration 有字段 max_step_numagent_typeconfigurable = {"max_step_num": 5}

values = {
    "max_step_num": 5,
    "agent_type": None,
    # ... 其他字段
}

例子 2:过滤无效值后创建对象

return cls(**{k: v for k, v in values.items() if v})

agent_type: None 被过滤掉 → Configuration 使用默认 AgentType.DEFAULT


8. 和其他写法对比

8.1 dict comprehension vs 普通循环

# 推导式(简洁,适合简单逻辑)
{x: x * 2 for x in range(5)}

# 普通循环(复杂逻辑时更清晰)
result = {}
for x in range(5):
    if some_complex_check(x):
        result[x] = x * 2

建议: 逻辑简单用推导式;嵌套太多、分支太多就改回 for 循环,可读性更重要。

8.2 过滤:if v vs if v is not None

# if v:None、""、0、[] 都会过滤掉
{k: v for k, v in d.items() if v}

# if v is not None:只过滤 None,保留 0 和 ""
{k: v for k, v in d.items() if v is not None}

项目里用 if v 是因为:空值/None 都表示「没配置,用默认值」。

8.3 与 dict() + 生成器

dict((k, v) for k, v in values.items() if v)  # 也可以,但不如 {} 直观

9. 常见误区

误区 1:键重复,后面的覆盖前面的

{x // 2: x for x in [1, 2, 3, 4]}
# {0: 4, 1: 3}   ← 不是 4 个键,因为键冲突了

误区 2:把 if 位置写错

# 过滤(for 后面的 if)
{k: v for k, v in d.items() if v > 0}

# 条件表达式(键或值里的 if else)
{k: (v if v else 0) for k, v in d.items()}

两种 if 含义不同:

位置 作用
for ... if 条件 过滤,不满足的不进 dict
值 if 条件 else 其他 三元表达式,决定值是什么

误区 3:推导式里副作用过多

# 不推荐:在推导式里 print / 写文件
{ k: print(k) or v for k, v in d.items() }

推导式适合纯转换,有副作用用普通 for 循环。


10. 小练习

练习 1: 列表转 dict,值为长度的 2 倍

words = ["ai", "agent", "mcp"]
# 目标: {'ai': 4, 'agent': 10, 'mcp': 6}
result = {w: len(w) * 2 for w in words}

练习 2: 只保留值大于 10 的项

scores = {"math": 90, "english": 5, "python": 88}
# 目标: {'math': 90, 'python': 88}
passed = {k: v for k, v in scores.items() if v > 10}

练习 3: 模拟项目里的过滤 + 解包

values = {"max_step_num": 5, "agent_type": None, "locale": "zh-CN"}

def fake_config(**kwargs):
    print(kwargs)

fake_config(**{k: v for k, v in values.items() if v})
# 输出: {'max_step_num': 5, 'locale': 'zh-CN'}

11. 速查表

# 基本
{k: v for k, v in pairs}

# 从列表
{str(x): x for x in range(3)}

# 带过滤
{k: v for k, v in d.items() if 条件}

# 变换键或值
{k.upper(): v * 2 for k, v in d.items()}

# 配合 ** 传参
func(**{k: v for k, v in d.items() if v})

12. 小结

概念 记住
基本语法 {键: 值 for x in 可迭代对象}
过滤 {k: v for k, v in d.items() if 条件}
.items() 同时拿到键和值
if v 去掉 None、空串、0 等 falsy 值
**{...} 把 dict 展开成关键字参数

字典推导式 = 用一行 for 循环生成 dict。读懂它,就能看懂项目里 Configuration.from_runnable_config 的配置合并逻辑。

记录日期:2026-05-28

更多推荐