该用[...]当需多次遍历、随机访问或索引切片;用(...)当仅单次遍历、数据量大或作中间管道。生成器不可pickle、不能重复使用,且需谨慎处理stopiteration。

什么时候该用 [...] 而不是 (...)
列表推导式生成的是完整列表,立刻分配内存;生成器表达式返回迭代器,按需产出。关键看你要不要「多次遍历」或「随机访问」。
- 需要索引(
res[0])、切片(res[:3])、重复遍历(比如先len()再循环)→ 用[...] - 只遍历一次、数据量大、或只是作为中间管道(比如传给
sum()、max())→ 用(...)更省内存 - 写成
(x for x in seq if cond)却误以为是元组 → 实际是生成器,不是tuple;要元组得显式调用tuple(...)
next() 和 StopIteration 是生成器绕不开的坑
生成器表达式本身不报错,但一旦耗尽再调用 next() 就会抛 StopIteration。很多人在封装函数时忽略这点,导致调用方崩溃。
- 直接解包(
a, b = gen)或传给只接受有限项的函数(如zip())时,生成器提前结束就会静默截断,不易察觉 - 调试时用
list(gen)看内容?行,但会把生成器“消耗掉”,后续再用就空了 - 想安全取首项?别写
next(gen),改用next(gen, None)提供默认值
嵌套推导式里括号多一层,语义就全变
生成器表达式不能像列表推导式那样自然嵌套多层逻辑,尤其容易在条件过滤和多重 for 上混淆。
-
[x for a in A for x in a]是扁平化,等价于两层for;而(x for a in A for x in a)行为一致,但仍是单个生成器 - 错误写法:
(x for x in range(10) if x % 2 == 0 for y in [1])—— 多余的for y不报错,但语义混乱,实际等价于if后拼接了一个恒真循环,容易误导 - 带
if的生成器传给all()或any()没问题;但传给map()或filter()前得确认它没被提前消费
性能差异在小数据上根本测不出来
别为了“理论上更省内存”在 100 个元素的场景硬用生成器。Python 解释器开销、函数调用、对象创建成本远高于那几百字节的差别。
立即学习“Python免费学习笔记(深入)”;
- 用
timeit测[x*2 for x in range(100)]和(x*2 for x in range(100)),时间几乎一样;但前者可反复用,后者一用就废 - 真正有影响的场景:读大文件逐行处理(
(line.strip() for line in f))、数据库游标结果集、递归展开深层嵌套结构 - 一个常被忽略的点:生成器表达式不能被 pickle,跨进程传递或缓存时会失败;列表可以
next()、谁来捕获 StopIteration、谁记得它只能用一次。









