Python 列表推导式和生成器表达式有什么区别?什么时候该用哪个?
区别就一个字:方括号 [] 立即算出所有结果放进列表,圆括号 () 返回一个生成器对象,用到哪个才算哪个。[x**2 for x in range(10)] 执行完内存里就有 10 个数;(x**2 for x in range(10)) 执行完只多了一个 200 字节的生成器对象,值还没算。
追问
生成器只能遍历一次,踩过坑吗?
这是最常见的陷阱。你写 gen = (x for x in range(5)),第一次 list(gen) 得到 [0,1,2,3,4],再 list(gen) 就是 []——生成器耗尽了。如果后面的代码还要用,要么转成列表存起来,要么重新创建生成器。调试时这个坑尤其烦人:你在调试器里 print(list(gen)) 看了一眼,后面代码就拿不到数据了。
sum(x2 for x in range(N)) 和 sum([x2 for x in range(N)]) 哪个快?
大多数人觉得生成器快,实际不一定。生成器省内存是肯定的,但每次 yield 有函数调用开销。列表推导式的循环在 C 层执行(CPython 实现中 listcomp 是专用字节码),而生成器每次 yield 要切换栈帧。数据量小时列表版反而更快——省下的内存分配开销比 yield 开销小。数据量大时生成器版才赢,因为列表版要先把所有结果存内存。经验值:N < 1000 差别可以忽略,N > 10 万生成器才有明显优势。
字典推导式和集合推导式呢?
语法一样,换括号就行:{k: v for k, v in pairs} 是字典推导式,{x for x in items} 是集合推导式。注意集合推导式没有生成器版本——{x for x in items} 是立即求值的,没有惰性集合。如果需要惰性去重,得用生成器 + set() 分两步。
嵌套推导式怎么读?
从左到右读,和嵌套 for 循环的顺序一致。[f(x,y) for x in A for y in B] 等价于 for x in A: for y in B: f(x,y)。超过两层就该换普通循环了,没人能在脑内解析三层推导式。Python 之禅说得明白:可读性很重要。
写段代码
python# 生成器只能遍历一次的坑 gen = (x**2 for x in range(5)) print(list(gen)) # [0, 1, 4, 9, 16] print(list(gen)) # [] ← 耗尽了! # 需要多次使用就转列表 squares = [x**2 for x in range(5)] print(squares[:3]) # [0, 1, 4] print(squares[3:]) # [9, 16] # 管道式处理用生成器省内存 lines = (line.strip() for line in open('big.log')) # 不读全文 errors = (line for line in lines if 'ERROR' in line) count = sum(1 for _ in errors) # 只计数,不存结果