1. 推导式是什么? #
- 推导式(comprehension) 用一行表达式从可迭代对象生成新容器。
- 结构上是「
for循环 + 可选if条件 + 表达式」的语法糖,等价于手写循环里append、赋值组 dict/set,但更紧凑、往往更快。 - 项目里常用于:过滤列表、映射字段、从接口/ORM 返回的
list[dict]提取某一列、轻量重组字典。 - 推导式不是炫技:逻辑简单时可读性很好;逻辑复杂时应退回普通
for。 - Python 提供四种形式,下表对比语法与结果类型(生成器用圆括号,惰性求值)。
| 类型 | 语法 | 结果 |
|---|---|---|
| 列表 | [expr for x in iterable if 条件] |
list |
| 字典 | {k: v for x in iterable if 条件} |
dict |
| 集合 | {expr for x in iterable if 条件} |
set |
| 生成器 | (expr for x in iterable if 条件) |
generator |
2. 列表推导式 #
- 列表推导式语法
[表达式 for 变量 in 可迭代对象 if 条件],if可省略。 - 表达式可以是计算结果、三元运算
a if cond else b,也可以是字典取值u["name"]。 - 多个
for从左到右嵌套,等价于外层for包内层for(如展平二维列表)。 - 下面依次演示:基本映射与过滤、从对象列表取字段、二维展平及与普通循环的等价写法。
# 基本映射:生成 0~4 的平方
squares = [x ** 2 for x in range(5)]
print(squares)
# 带 if 过滤:保留偶数
evens = [x for x in range(10) if x % 2 == 0]
print(evens)
# 表达式里三元运算
labels = ["even" if x % 2 == 0 else "odd" for x in range(3)]
print(labels)- 从对象列表取字段(接口、ORM 查询结果极常见):
# 用户列表
users = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]
# 提取所有用户名
names = [u["name"] for u in users]
# 提取 active 用户的 id
active_ids = [u["id"] for u in users if u.get("active")]二维列表展平:
列表推导式
[n for row in matrix for n in row]中的for子句顺序从左到右书写,相当于嵌套的普通for循环。- 外层循环:
for row in matrix→ 依次取出[1, 2]和[3, 4]。 - 内层循环:
for n in row→ 遍历当前行中的每个元素。 - 最终按顺序收集内层循环的变量
n,形成新列表。
# 二维列表
matrix = [[1, 2], [3, 4]]
# 展平为一维列表
flat = [n for row in matrix for n in row]- 如果不使用推导式,上面代码等价于:
# 用普通循环展平二维列表
flat = []
for row in matrix:
for n in row:
flat.append(n)3. 字典推导式 #
- 字典推导式语法
{键表达式: 值表达式 for ... if 条件},从可迭代对象构造新字典。 - 常见用途:过滤原 dict(只保留及格分)、改键名/改值、把列表转成
{id: obj}索引。 - 遍历已有字典时常写
for k, v in d.items()。 - 值表达式里可用三元运算做「条件赋值」,如不及格记为
None,而不必先建空 dict 再循环if填入。
# 基本:生成数字到平方的映射
squares = {x: x ** 2 for x in range(5)}
# 过滤原字典:只保留及格分
scores = {"Alice": 85, "Bob": 58, "Charlie": 71}
passed = {name: score for name, score in scores.items() if score >= 60}- 按条件改值:
# 不及格分数改为 None
result = {
name: score if score >= 60 else None
for name, score in scores.items()
}4. 集合推导式 #
- 集合推导式语法
{表达式 for ... if 条件},注意与字典推导式区分:字典是{k: v for ...}带冒号,集合只有一个表达式。 - 结果自动去重,适合从带重复的标签列表生成唯一集合,或带条件过滤后再去重。
- 简单全体去重也可
set(lst);需要if条件时推导式更顺手。
# 标签列表(含重复)
tags = ["py", "web", "py", "api"]
# 生成唯一标签集合
unique = {t for t in tags}- 与
set(tags)效果类似,带条件时推导式更方便。
5. 生成器表达式 #
- 生成器表达式语法与列表推导式相同,但用圆括号
():(expr for x in iterable if cond)。 - 它不会立刻算出全部结果,而是按需逐个产生元素(惰性求值),占内存小。
- 适合大数据或只遍历一次的场景,常直接传给
sum()、any()、max()等消费迭代器的函数。 - 若写成
list(...)则失去惰性,与列表推导式类似。 - 下面示例中,定义
double时带print,可见只有list(evens)消费时才真正执行循环体。
# 定义带打印副作用的函数
def double(x):
print(f"Doubling {x}...")
return x * 2
# 生成器表达式:惰性求值,不会立即执行
evens = (double(x) for x in range(10) if x % 2 == 0)
print(evens)
# 消费生成器,转为列表时才真正执行
print(list(evens))6. 何时用、何时不用 #
- 判断是否用推导式,核心看一行能否说清楚且无副作用。
- 适合:字段映射、条件过滤、拼 URL、从
list[dict]提 ID 等「对每个元素做同一规则」的操作。 - 不适合:多层嵌套、需要
try/except、多步中间变量、循环里写库/打日志等有副作用的流程。 这类应写显式
for或多行函数,便于调试和 Code Review。适合示例:
# 从用户列表生成 API URL
urls = [f"/api/users/{u['id']}" for u in users]不适合(改用普通
for或多行函数):逻辑复杂、嵌套超过两层。
- 需要
try/except、多步中间变量、副作用(写库、打日志)。 - 读起来需要反复推敲的一行长表达式。
7. 项目开发要点 #
- 推导式是 Python 里「写得短」的代表,但团队项目里可读性优先。
- 列表推导式最常用:清洗字段、过滤列表、拼 URL、提取 ID。
- 字典推导式:过滤配置、重组
dict、接口返回前的轻量转换。 - 集合推导式:去重;简单去重也可
set(lst)。 - 生成器表达式:大列表统计、链式
sum/any;不要无谓list(gen)占内存。 - 可读性优先:一行写不下就换
for,团队代码审查会看这点。 - 推导式里不宜塞副作用:
[db.save(x) for x in items]应避免,用显式循环。
8. 总结 #
- 四种推导式共享同一套
for/if思维,区别在外层括号与结果类型。 []列表、{}键值 dict、单表达式{}set、()惰性生成器。- 日常以列表与字典推导为主;拿不准时改用普通
for即可。 - 下表按类型速查典型场景。
| 类型 | 典型场景 |
|---|---|
列表 [...] |
映射、过滤、提取字段 |
字典 {...} |
过滤/改造字典 |
集合 {...} |
去重、唯一值 |
生成器 (...) |
大数据、传给 sum/any 等 |