代码语言

知识点思维导图

21 个知识节点

Python(06) - 列表推导式与生成器

读完后,你应能完成以下任务:

  • 绘制“Python(06) - 列表推导式与生成器 / 先建立前端锚点”的关键对象与数据流,解释“核心切入点:列表推导式 ≈ map/filter 的合体语法糖,生成器 ≈ JS 的 function 一对一映射。”,并用源码位置、日志或 Trace 标注证据。
  • 为“Python(06) - 列表推导式与生成器 / 列表推导式:map + filter 的合体”设计正常与异常输入,验证“读法对照:把 nums.map(x => x 2) 拆成三段——x 2(要算什么)、x(每个元素叫什么)、nums(从哪来),重新排成 [x 2 for x in nums] 就是 Python 写法。”,输出首个偏差位置与回归测试结果。
  • 实现“Python(06) - 列表推导式与生成器 / map + filter 一次写完”的最小代码或配置,检验“注意顺序:Python 推导式里 for...if 在后、要算的表达式在前,和阅读习惯相反,但写多了就顺手了。”,输出命令、结果与 Diff,并说明不适用边界。

你在 JS 里写惯了 arr.map().filter(),到了 Python 会发现大家很少这么链式写,而是用一种叫"列表推导式"的语法糖。本篇解决两个问题:怎么用比 map/filter 更地道的方式处理集合;以及当数据量大到不想一次性装进内存时,怎么用 yield 做"惰性流"。后者直接对标你见过的 function*

一、先建立前端锚点

你在 JS 里这么做 Python 地道写法 类别
arr.map(x => x * 2) [x * 2 for x in arr] 列表推导式
arr.filter(x => x > 0) [x for x in arr if x > 0] 带条件的推导式
function* gen() { yield 1 } def gen(): yield 1 生成器
for (const v of iterable) for v in iterable 迭代

核心切入点:列表推导式 ≈ map/filter 的合体语法糖,生成器 ≈ JS 的 function* 一对一映射。下面分别讲透。


二、列表推导式:map + filter 的合体

2.1 最基础:等价于 map

并排看 JS:

const nums = [1, 2, 3, 4]
const doubled = nums.map(x => x * 2)   // [2, 4, 6, 8]

读法对照:把 nums.map(x => x * 2) 拆成三段——x * 2(要算什么)、x(每个元素叫什么)、nums(从哪来),重新排成 [x * 2 for x in nums] 就是 Python 写法。

2.2 加条件:等价于 filter

const nums = [1, 2, 3, 4, 5, 6]
const evens = nums.filter(x => x % 2 === 0)   // [2, 4, 6]

2.3 map + filter 一次写完

JS 里 map 和 filter 要链两次,Python 一个推导式搞定:

const nums = [1, 2, 3, 4, 5, 6]
const result = nums.filter(x => x % 2 === 0).map(x => x * 10)  // [20, 40, 60]

注意顺序:Python 推导式里 for...if 在后、要算的表达式在前,和阅读习惯相反,但写多了就顺手了。

2.4 字典推导式 / 集合推导式

把方括号换成花括号,就能直接生成 dict 或 set:

JS 没有等价语法糖,得手动 reduce 或 Object.fromEntries

const names = ["tom", "jerry"]
const nameLen = Object.fromEntries(names.map(n => [n, n.length]))  // {tom:3, jerry:5}

三、边界:哪里和 JS 不一样

类比建立了直觉,但必须立刻划清差异,别过度套用 JS 心智:

  1. 没有 [].map 的链式语法主流地位。Python 也有 map()/filter() 内置函数,但它们返回的是"惰性迭代器"而非列表,且社区公认推导式更可读。能用推导式就别用 map/filter

  2. 推导式里的变量不泄漏到外层(Python 3 起)。下面的 x 不会污染外部作用域,这点和 JS 的 let 块级作用域一致,但和 Python 普通 for 循环不同——普通 for 的循环变量会留在外面。

  3. 别为了炫技写嵌套三层推导式。可读性崩了就老老实实用普通 for。推导式适合"一行能说清"的转换。

  4. range(n) 不是数组,是惰性的范围对象。[i for i in range(3)] 才得到列表 [0,1,2]。它类似 JS 里没有的"惰性 0..n 序列"。


四、生成器:yield 一对一对标 function

4.1 心智模型直接搬 JS

你在 JS 里见过这个吗?

function* countUp() {
  yield 1          // 暂停在这,把 1 交出去
  yield 2
  yield 3
}
const it = countUp()
it.next().value    // 1,每次 next 才往下走一步

Python 几乎逐字对应,只是去掉了 *,函数体里出现 yield 它就自动成为生成器:

对照表:

JS Python
function* g() {} def g(): ...(函数内含 yield)
yield v yield v
it.next().value next(it)
for (const v of g()) for v in g()
迭代结束 done: true 抛出 StopIterationfor 会自动处理)

4.2 为什么要用它:惰性 + 省内存

普通函数是"一次性把结果全做完返回",生成器是"要一个给一个"。处理大数据时差别巨大:

如果用列表,["..." for line in ...] 会把几百万行全装进内存;生成器则是流式的,内存占用恒定。这正是 function* 在 JS 里处理无限/超大序列的同款理由。

4.3 生成器表达式:推导式的惰性版

把列表推导式的方括号 [] 换成圆括号 (),就从"立刻算出整个列表"变成"惰性生成器":

经验法则:只是要遍历/聚合一次就用生成器表达式 ();需要反复访问、索引、求长度才用列表 []


五、最容易踩的坑

  1. 生成器只能消费一次。遍历完就空了,不像列表能反复 for。这是新手最常见的崩溃点。

    对照 JS 的 generator 也是同样行为——迭代器一旦走完就 done。需要复用就转成列表存起来。

  2. 想要列表却写成了生成器(x for x in nums) 不是元组,是生成器;print 它会看到 <generator object ...> 而不是内容。要列表请用 [],或外面包 list(...)

  3. 推导式里调用有副作用的函数。推导式应当是"纯转换"。如果你在里面 print 或改外部状态,说明该用普通 for

  4. 超大数据还用列表推导式。这会瞬间吃满内存。判断标准:源数据大 + 只遍历一次 → 用生成器表达式 ()yield


六、综合示例:读日志统计错误

把本篇知识串起来,写一个流式统计:


七、总结

  • 列表推导式:map + filter 的合体:读法对照:把 nums.map(x => x 2) 拆成三段——x 2(要算什么)、x(每个元素叫什么)、nums(从哪来),重新排成 [x 2 for x in nums] 就是 Python 写法。
  • 边界:哪里和 JS 不一样:没有 [].map 的链式语法主流地位。 -> 推导式里的变量不泄漏到外层(Python 3 起)。 -> 别为了炫技写嵌套三层推导式。 -> range(n) 不是数组,是惰性的范围对象。
  • 生成器:yield 一对一对标 function:普通函数是"一次性把结果全做完返回",生成器是"要一个给一个"。
  • 最容易踩的坑:生成器只能消费一次。遍历完就空了,不像列表能反复 for。这是新手最常见的崩溃点。 -> 想要列表却写成了生成器。(x for x in nums) 不是元组,是生成器;print 它会看到 而不是内容。要列表请用 [],或外面包 list(...)。 -> 推导式里调用有副作用的函数。 -> 超大数据还用列表推导式。这会瞬间吃满内存。判断标准:源数据大 + 只遍历一次 → 用生成器表达式 () 或 yield。
  • 生成器表达式:推导式的惰性版:经验法则:只是要遍历/聚合一次就用生成器表达式 ();

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“列表推导式与生成器”中,需要同时满足“先建立前端锚点”与“最基础:等价于 map”。给定正文约束“列表推导式 ≈ map/filter 的合体语法糖,生成器 ≈ JS 的 function 一对一映射。”,哪些判断保持了原有处理机制?多选
2“列表推导式与生成器”出现偏差:“在“列表推导式与生成器 / map + filter 一次写完”中,即使不满足“JS 里 map 和 filter 要链两次,Python 一个推导式搞定”,结果与副作用仍会保持不变。”已成为实际行为。围绕“map + filter 一次写完”与“字典推导式 / 集合推导式”,哪些判断能定位被改变的职责或边界?多选
3评审“列表推导式与生成器”方案时,验收条件包含“类比建立了直觉,但必须立刻划清差异,别过度套用 JS 心智。”。关于“边界:哪里和 JS 不一样”与“心智模型直接搬 JS”的哪些决策符合正文机制?多选