4.4 推导式与解包
前置:4.1–4.3 · 约 30 分钟 · 官方对应:列表推导式、PEP 448 解包扩展
目标
- 把列表推导式“翻译”成等价的
for循环,反之亦然。 - 读懂字典推导式、集合推导式、生成器表达式与嵌套推导式。
- 读懂字面量中的
*与**解包合并。
核心概念
列表推导式
[表达式 for 变量 in 可迭代对象 if 条件],按 for → if → 表达式 的顺序读:
Python · 可运行
python
nums = [1, 2, 3, 4, 5, 6]
squares_of_even = [n * n for n in nums if n % 2 == 0]
print(squares_of_even)
# 完全等价的循环写法
result = []
for n in nums:
if n % 2 == 0:
result.append(n * n)
print(result)if 放在末尾是过滤;如果需要“二选一”,条件表达式放在前面:
Python · 可运行
python
nums = [1, 2, 3, 4]
print([n for n in nums if n > 2]) # 过滤:[3, 4]
print(["大" if n > 2 else "小" for n in nums]) # 映射:['小', '小', '大', '大']字典推导式、集合推导式
Python · 可运行
python
tools = [{"name": "get_weather"}, {"name": "get_time"}]
name_to_index = {t["name"]: i for i, t in enumerate(tools)}
print(name_to_index)
prices = {"a": 10, "b": 25, "c": 5}
expensive = {k: v for k, v in prices.items() if v >= 10}
print(expensive)
types = {b["type"] for b in [{"type": "text"}, {"type": "tool_use"}, {"type": "text"}]}
print(sorted(types))生成器表达式
把方括号换成圆括号就是生成器表达式:不立即生成列表,而是按需逐个产生值(详见 10.1)。直接作为函数唯一参数时可以省略外层括号:
Python · 可运行
python
nums = range(1, 1_000_001)
print(sum(n * n for n in nums)) # 不构造百万元素的列表
print(any(b["type"] == "tool_use" for b in [{"type": "text"}, {"type": "tool_use"}]))
print(", ".join(str(n) for n in range(5)))嵌套推导式
多个 for 从左到右对应循环的从外到内:
Python · 可运行
python
messages = [
{"role": "assistant", "content": [{"type": "text"}, {"type": "tool_use"}]},
{"role": "user", "content": [{"type": "tool_result"}]},
]
all_types = [block["type"] for msg in messages for block in msg["content"]]
print(all_types)
# 等价循环
out = []
for msg in messages:
for block in msg["content"]:
out.append(block["type"])
print(out == all_types)字面量中的解包
Python · 可运行
python
base = ["user"]
roles = [*base, "assistant", *("tool",)] # 列表中展开可迭代对象
print(roles)
defaults = {"max_tokens": 1024, "temperature": 1.0}
request = {**defaults, "model": "deepseek-flash", "temperature": 0} # 后出现的键覆盖前面的
print(request)易错点
- 推导式过长:超过一行、嵌套两层以上或带副作用时,改写成普通循环更清晰。
- 在推导式中做副作用(如
[print(x) for x in xs]):会构造一个无用的列表,直接用for。 - 生成器只能遍历一次:第二次遍历为空。
- 过滤与映射位置写错:
[x for x in xs if c else y]是语法错误。
Python · 可运行
python
gen = (n for n in range(3))
print(list(gen))
print(list(gen)) # []:已经耗尽对照
| 写法 | Python | JS | Java Stream |
|---|---|---|---|
| 映射 | [f(x) for x in xs] | xs.map(f) | xs.stream().map(f).toList() |
| 过滤 | [x for x in xs if p(x)] | xs.filter(p) | .filter(p) |
| 过滤+映射 | [f(x) for x in xs if p(x)] | xs.filter(p).map(f) | .filter(p).map(f) |
| 构造映射 | {k(x): v(x) for x in xs} | Object.fromEntries(xs.map(...)) | Collectors.toMap(...) |
| 展平 | [y for x in xs for y in x] | xs.flat() / flatMap | flatMap |
| 惰性 | 生成器表达式 | 迭代器 helpers | Stream 本身是惰性的 |
读代码
minimal_agent.py 中:
python
final_text = "".join([b.text for b in resp.content if b.type == "text"])按 for → if → 表达式读:遍历 resp.content 中的每个块 b;只保留 b.type == "text" 的;取出 b.text;最后用 "" 连接。这里的方括号可以省略(写成生成器表达式),效果相同。
工具执行部分也可以写成推导式:
python
results = [
{"type": "tool_result", "tool_use_id": b.id, "content": TOOL_FUNCTIONS[b.name](**b.input)}
for b in resp.content
if b.type == "tool_use"
]示例代码改成了显式 for 循环,是为了在执行每个工具时打印日志——有副作用时用循环,这正是上面“易错点”的原则。
练习
练习 1:取出文本
content 是 dict 列表。用一个推导式实现 extract_text(content):返回所有 type == "text" 的块的 text 拼接结果。
练习
def extract_text(content):
...
快捷键:⌘/Ctrl + Enter 提交检查
练习 2:构造 tool_result
实现 make_results(content, functions):对每个 type == "tool_use" 的块,调用 functions[块["name"]](**块["input"]),生成 {"type": "tool_result", "tool_use_id": 块["id"], "content": 结果},返回列表。
练习
def make_results(content, functions):
return []
快捷键:⌘/Ctrl + Enter 提交检查
练习 3:反向索引
实现 invert(d):把 {键: 值} 反转为 {值: 键}(假设值唯一),使用字典推导式。
练习
def invert(d):
return d
快捷键:⌘/Ctrl + Enter 提交检查
小结
- 推导式按 for → if → 表达式读,可逐字翻译为循环。
[]列表、{k: v}字典、{x}集合、()生成器(惰性、只能遍历一次)。[*a, *b]、{**a, **b}在字面量中展开合并。- 有副作用或逻辑复杂时用普通循环。