Python 推导式与生成器表达式
列表/集合/字典推导与生成器表达式用声明式构造数据;注意作用域、可读性与惰性求值边界。
#type / concept
#status / growing
#tech / dev
#resource / python
#tech / lang / python
[!info] 关联笔记
Python 推导式与生成器表达式
这个概念为什么出现
从一批订单过滤出待发货 ID、从日志抽状态码——用 for + append 冗长。推导式把“映射/过滤”压缩为表达式;生成器表达式则惰性产出,避免一次性物化大列表。
[!abstract] 一句话理解 推导式声明式构建 list/set/dict;生成器表达式用圆括号惰性产出元素,可迭代一次。
最小可运行示例
先把示例放进业务场景,再看代码:
场景:订单中心筛选可发货单并统计渠道
# orders_comprehension.py
# 业务意图:过滤 paid 订单、映射发货任务、统计渠道集合。
# 教学点:
# - list/set/dict 推导;
# - 生成器表达式节省中间列表;
# - 可读性:复杂逻辑不要硬塞一行。
def main() -> None:
orders = [
{"id": "O1", "status": "paid", "channel": "app"},
{"id": "O2", "status": "cancel", "channel": "web"},
{"id": "O3", "status": "paid", "channel": "app"},
]
ship_ids = [o["id"] for o in orders if o["status"] == "paid"]
channels = {o["channel"] for o in orders}
status_index = {o["id"]: o["status"] for o in orders}
total_paid = sum(1 for o in orders if o["status"] == "paid") # 生成器表达式
print("ship:", ship_ids)
print("channels:", channels)
print("index:", status_index)
print("paid count:", total_paid)
if __name__ == "__main__":
main()
建议运行:
python orders_comprehension.py
期望输出:
ship: ['O1', 'O3']
channels: {'app', 'web'}
index: {'O1': 'paid', 'O2': 'cancel', 'O3': 'paid'}
paid count: 3
(channels 集合展示顺序可能不同。)
结合场景再看三个关注点
- 过滤 + 映射是推导式主场。
sum(1 for ...)不必先建 list。- 嵌套三层推导通常应拆函数。
核心概念与准确模型
[expr for x in xs if cond]
{expr for x in xs}
{k: v for x in xs}
(expr for x in xs) # generator
- 推导式有独立作用域(3.x 列表推导循环变量不泄漏)
- 可嵌套 for,但要克制
边界情况与反直觉行为
- 生成器耗尽后为空,不能当列表反复遍历除非物化。
- 海象
:=可在推导中赋值,但易损害可读性。 - 异常在迭代时才抛出(生成器)。
常见误区
[!warning] 常见误区:所有循环都改成推导 错误理解:推导一定更 Pythonic。
正确模型:有副作用(写库、打印)的流程用 for;纯映射过滤用推导。
工程实践
- 超过两层条件拆函数命名。
- 大数据管线优先生成器/迭代器链。
- 与
map/filter二选一,团队统一风格。
本节总结
推导式提升表达密度;生成器表达式控制内存。可读性永远优先于“一行装下宇宙”。
自测题
- 列表推导与生成器表达式在内存上的关键差别?
- 何时不该用推导?
参考答案
- 列表立即物化全部元素;生成器按需产出。
- 需要多语句、复杂分支或明显副作用时。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Data Structures – List Comprehensions | 教程 | 推导式 |
| Generator expressions | 规范 | 语法 |