Python 09:yield 如何保存未完成的计算
词频任务收到三段合成文本,调用 iter_words(chunks()) 后,上游读入记录仍为空;取出第一个词后,记录里只有第一段 "One "。代码不是跳过了后两段,生成器只是停在第一个 yield,直到下一次请求才继续执行剩下的计算。
08 的任务集合已经区分可迭代对象与一次遍历。09 用同一个 tasklab 的 word_count 负载替换一次性 text.lower().split():词元从一个按需产出的生成器进入计数循环,结果合同保持不变。
建立对象时不等于读取输入
1 | |
调用包含 yield 的函数时,得到生成器对象,函数体并未运行到读取第一段的语句。next(words) 使它运行到第一个词后的 yield 并挂起,下一次取词才从挂起点继续。Python 3.12 的 yield 表达式规范说明挂起、恢复和关闭的基本语义,生成器教程给出 for 调用 next 的常见用法。
实测 before_next: [],第一次 next 得到 "one",after_next: ["One "];将剩余生成器转成列表才依次得到 "two"、"one"。本实验用 read.append(chunk) 记录上游真正交出的块,不靠耗时推断“可能是惰性的”。iter_words 能处理跨块的单词和空白,但每个未完结单词仍暂存在 letters 中;惰性不代表运行时内存为零。
不为节省列表牺牲原任务合同
run 现在从 iter_words((task["text"],)) 计数,仍接受 00 起的字符串任务输入,返回按空白分词并小写后的词频。"One two one" 的结果仍是 {"one": 2, "two": 1};测试还比较空文本、包含制表符和换行的文本、含中文的文本与旧 lower().split() 基线。当前 submit 已将整个短文本放进内存,没有因为词元按需生成就让输入自动变成文件流。16 会引入由资源所有者负责关闭的文件作用域。
提前消费后调用 words.close(),本章测试证明该词元生成器没有再读取下一段,而且不能再次迭代得到余下词元。但它并没有打开文件,更不能由“没有继续读块”推出“外层文件资源已经安全关闭”。如果上游持有真实文件句柄,只关闭一层生成器是否传播到资源所有者,仍须显式设计并分别验证正常结束、异常和提前退出。
从 examples/python-language/ 运行:
1 | |
两条命令退出码均为 0,累计测试 29 条。本章增量测试以读取块数而非表面结果证明提前停止时没有消耗所有上游数据,以旧实现为预期结果对照,另检查显式关闭生成器后不会继续读取。原始输出及其环境在 examples/python-language/evidence/09/20261007-generator/。这份记录没有把文件句柄的关闭计入已通过。
预测题
上游第一块改成 "One",第二块改成 " two "。只请求第一个词时,读取记录会出现几块?
答案:第一块没有结束词元,生成器无法先交付 "one";必须读到第二块开头的空白才能 yield,因此记录会有两块。原始实验使用带尾部空白的 "One ",上述跨块条件为预测变式,尚未执行。
变更题
在取出第一个词后直接丢掉生成器对象,不调用 close();若上游改成文件迭代器,能以这项动作证明文件已经关闭吗?
答案:不能。垃圾回收时机、生成器生命周期与文件所有权不能靠一次解释器上的偶然观察替代。应由持有文件的作用域明确调用关闭,再在提前终止、抛异常及正常读取三种路径分别断言 file.closed。16 才会加入这些资源证据,本章未执行。
下一章在词元生成器上组合过滤和批处理,再用读取计数排除管道重复消费。系列导航只链接实际页面。






