- 教程
- 文档
【免费下载链接】30-seconds-of-python
Short Python code snippets for all your development needs
unfold是 30-seconds-of-python 仓库中一个典型的函数式列表构建工具:它不直接枚举元素,而是借助一个「迭代器函数 + 初始种子值」的组合,反复推进状态、产出元素,直到满足终止条件。本文以仓库文档 snippets/unfold.md 为骨架,逐行拆解其实现原理、返回值契约与示例推演,并对照仓库中fibonacci、geometric-progression、daterange等同类列表生成片段,帮助你彻底掌握这一简洁而强大的 Python 惯用法。
什么是 unfold:从种子值「展开」出列表
在函数式编程中,fold(归约)与unfold(展开)是一对方向相反的操作:
- fold/reduce把一组值逐步合并成一个结果,例如仓库中的 lcm.md 用
functools.reduce()把一组数字归约为最小公倍数; - unfold则恰好相反:从一个初始种子值出发,反复调用迭代器函数,把每次产出的
value收集起来,形成一个列表。
unfold适合那些「由当前状态推算出下一步状态」的生成场景——序列的长度不事先确定,而是由迭代器函数内部的终止条件动态决定。这与使用range()+ 列表推导式一次性算好长度的方式(如 geometric-progression.md)形成了鲜明的互补。
核心实现逐行解析
仓库中unfold的完整实现如下(来自 snippets/unfold.md):
def unfold(fn, seed): def fn_generator(val): while True: val = fn(val[1]) if val == False: break yield val[0] return [i for i in fn_generator([None, seed])]整个实现由三部分构成,可以对照逐行理解:
- 外层函数签名:
unfold(fn, seed)接收两个参数——迭代器函数fn与初始种子值seed。 - 内部生成器
fn_generator(val):这是一个标准的生成器函数,使用while True构成无显式上限的循环:- 每次迭代先执行
val = fn(val[1]),把当前种子的下一个状态交给迭代器函数处理; - 若返回值等于
False,立即break跳出循环,终止生成; - 否则通过
yield val[0]把本次产出的value吐给调用方。
- 每次迭代先执行
- 外层列表推导式:
[i for i in fn_generator([None, seed])]把生成器产出的所有value收集成最终列表。
这里有一个精巧的细节:生成器的初始输入是[None, seed]这个二元列表——val[1]首次取值即为seed,从而让fn的第一轮调用作用在种子值上,同时保持循环体内「先取val[1]、再产出val[0]」的统一形态。这种把种子打包进首轮输入的写法,让代码在没有任何特判分支的情况下完成了首轮初始化。
迭代器函数的返回值契约
文档对迭代器函数fn提出了严格的契约,这也是使用unfold前必须遵守的约定:
- 必须只接受一个参数(即当前种子
seed); - 必须返回一个包含两个元素的列表
[value, nextSeed],其中value是本次要产出到结果列表的元素,nextSeed是下一轮迭代使用的种子; - 或者返回
False以终止整个生成过程。
也就是说,fn每一次调用都在回答两个问题:「这次产出什么?」和「下一次基于什么状态继续?」。当它不再有内容可产出时,用False作为哨兵值通知生成器收尾。
值得注意的一点是:外层判断写的是if val == False,使用的是相等性比较而非is False或not val。由于 Python 中0 == False为True,如果迭代器函数返回整数0也会触发终止;但返回None、空列表等其它假值则不会终止。因此实际编写迭代器函数时,建议严格按文档约定返回字面量False,避免语义混淆。
示例推演:unfold(f, 10)
文档给出了如下示例:
f = lambda n: False if n > 50 else [-n, n + 10] unfold(f, 10) # [-10, -20, -30, -40, -50]迭代器函数f的逻辑是:当种子n > 50时返回False终止;否则返回[-n, n + 10]——即产出负值-n,并把种子推进到n + 10。整个执行过程可以逐步推演:
| 轮次 | 当前种子 | 调用fn的结果 | 产出的value |
|---|---|---|---|
| 1 | 10 | [-10, 20] | -10 |
| 2 | 20 | [-20, 30] | -20 |
| 3 | 30 | [-30, 40] | -30 |
| 4 | 40 | [-40, 50] | -40 |
| 5 | 50 | [-50, 60] | -50 |
| 6 | 60 | False(60 > 50) | ——(终止) |
于是生成器依次yield出-10, -20, -30, -40, -50,外层列表推导式收集后即得到最终结果。注意终止条件用的是严格大于(n > 50),因此种子恰好为 50 时仍会再产出最后一轮,这与break时机直接相关。
边界情况与使用注意
基于上述实现,可以总结出几个在实战中需要留意的边界行为:
- 立即终止:若迭代器函数对种子值直接返回
False,fn_generator在首轮即break,unfold返回空列表[],不会抛错。 - 永不终止的风险:由于外层是
while True且没有轮次上限,一旦迭代器函数在任何一轮忘记返回False,生成器将无限运行。在要求严谨的场景(如处理不可信输入)下,可以自行给fn_generator增加最大迭代次数的保险丝。 - 惰性求值的代价:生成器本身是惰性的,元素是逐个被拉取的;虽然外层用列表推导式一次性物化,但也可以改写为
list(fn_generator([None, seed]))达到相同效果。若后续只需要前 N 个元素,甚至可以直接消费生成器而无需构建完整列表。 - 种子的任意性:种子值
seed不限于数字——只要迭代器函数能够接收并更新它,任何 Python 对象(元组、列表、字典等)都可以作为状态载体,这为unfold的通用性留出了空间。
与仓库中其他列表构建片段的对比
unfold并非仓库中唯一生成列表的方式,把它与同类片段放在一起对比,更能看清各自的适用边界:
- fibonacci.md:用
while len(sequence) <= n+list.append()逐项累加,属于命令式写法,长度由外部参数n决定; - geometric-progression.md:用
math.log与math.floor先算出项数,再通过range()+ 列表推导式一次性构造,适合「长度可闭式计算」的等比数列; - daterange.md:用
(end - start).days确定天数,再以timedelta列表推导式生成日期序列; - roll.md:用切片
lst[-offset:] + lst[:-offset]对既有列表做旋转,属于对已有数据重排而非生成。
相比之下,unfold的独到之处在于:生成逻辑完全由迭代器函数自治——何时停止、产出什么、状态如何推进,全部内聚在fn一个函数里。当你需要「根据当前状态推出下一状态,直到满足某个动态条件」时,例如模拟递推序列、逐步展开嵌套结构、按规则迭代探索状态空间,unfold都是比手写while+append更简洁、更聚焦的抽象。
仓库规范:snippet 的书写约定
从实现细节还可以观察到 30-seconds-of-python 对片段代码的格式约定,这些规则在 CONTRIBUTING.md 中有明确要求,unfold本身即是符合规范的范例:
- 缩进:统一使用 2 个空格的软缩进(soft tabs),不使用硬 Tab;
- 风格:逗号后保留一个空格,变量名使用有意义的命名(如
seed、val),不使用首尾下划线; - 依赖:片段只允许使用 Python 3.6 原生模块,禁止引入外部库;
- 文件结构:每个片段是一个位于 snippets/ 目录下的 Markdown 文件,以 YAML frontmatter 声明元信息,正文则按 snippet-template.md 的格式组织:先以要点说明工作原理,再给出代码块与带注释的输出示例。
unfold.md的 frontmatter 就是典型样例:
--- title: Unfold list type: snippet tags: [function,list] cover: painters-desk dateModified: 2020-11-02T19:28:35+02:00 ---其中type: snippet与tags(以function为主标签)用于网站的分类与检索。另外根据 README.md 的说明,该仓库自 2023 年 5 月起已并入 30-seconds-of-code 组织统一维护,本文分析基于当前仓库中保留的片段源码。
小结
unfold用十余行代码展示了 Python 生成器在函数式数据生成上的优雅一面:迭代器函数定义「单步规则」,fn_generator负责「反复推进 + 哨兵终止」,外层列表推导式完成「收集物化」,三者职责清晰、彼此解耦。掌握它之后,遇到任何「由状态推状态、由条件定边界」的列表生成问题,都可以先想想能否用unfold一行解决,再决定是否退回手写循环。
- 教程
- 文档
【免费下载链接】30-seconds-of-python
Short Python code snippets for all your development needs
相关推荐
30-seconds-of-python 源码解析:用 collections.Counter 过滤列表非唯一值(filter_non_unique)
30 seconds of python 源码解析:用 collections.Counter 过滤列表非唯一值(filter_non_unique) 本文深入
教程文档30-seconds-of-python 之 Compact 列表压缩:用 `filter(None)` 一行代码清除假值
30 seconds of python 之 Compact 列表压缩:用 filter None 一行代码清除假值 本篇文章以 30 seconds of p
教程文档30-seconds-of-python 源码解析:如何用 sort_by_indexes 按索引列表对列表排序
30 seconds of python 源码解析:如何用 sort_by_indexes 按索引列表对列表排序 sort_by_indexes 是 30 se
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考