P2-10.3 把笔记本整理成可重新执行的记录¶
Section ID:
P2-10.3Version:v2026.07.23
在 P2-10.1 中,我们把 notebook 看成同时包含 code、explanation 和 output 的计算文档。在 P2-10.2 中,我们又从执行位置和文件访问的角度区分了 Jupyter、Colab 和本地执行。
现在再往前走一步。Notebook 作为学习记录很有用,但当 cell 被多次执行后,文档里看到的顺序和实际执行状态可能会分离。所以 notebook 必须同时被整理成 可读的文档 和 可复现的记录。
这里说明 reproducible record、execution order、hidden state、runtime state 的基本区分。关于 notebook 与 cell 结构的代表性说明放在 P2-10.1,执行位置差异放在 P2-10.2,而 reproducibility 的代表性说明放在 P2-7.5 和概念词汇表。这里关注的是:如何整理这些记录,让它们以后仍然值得信任。
放回 Part 2 的流程里看,Chapter 7 处理的是 在哪里执行,Chapter 8 到 9 处理的是 写什么、用什么句子写,而 Chapter 10 处理的是 怎样把这些执行和输出保留下来,并且以后还能再读。只有这个标准立起来,紧接着的 Chapter 11 到 14 才不会读成一串新工具名称,而会读成一种准备流程:在 notebook 里计算数组、读取表格、检查图,再用 Git 留下记录。
本节关注的不是 notebook 美化技巧,而是怎样把执行结果整理成以后还能继续相信的记录。如果前两节讨论的是 notebook 是什么,以及它在哪里运行,那么这里看的就是:要让 notebook 以后还能重新执行、重新解释,到底必须留下什么。这样一来,Chapter 11 到 14 里的工具也更容易被读成“重新确认计算和解释的记录流程”,而不是新的功能清单。
| 本节现在要抓住的内容 | 紧接着会延伸到的问题 | 之后再次出现的位置 |
|---|---|---|
| 好的 notebook 必须同时是可读文档和可重跑记录 | 会延伸到在 Chapter 11 到 14 里,计算、表格、可视化和 Git 记录应该按什么顺序保留 | 之后会在所有练习 notebook、Colab 共享和项目记录中反复出现 |
| cell 顺序和 hidden state 会改变结果 | 会延伸到为什么需要“重启后从上到下运行”的习惯 | 之后在调试、可复现性检查和协作共享中都持续重要 |
在 notebook 中验证过的代码,最终会到达应拆成函数和 .py 文件的时点 | 会延伸到如何判断记录和可复用代码的边界 | 之后会在工具脚本、项目结构和 Git 记录整理中再次使用 |
| 术语 | 本节先要抓住的含义 |
|---|---|
| reproducible record | 一种 notebook,即使以后重新打开,也能用同样流程重新得到执行和解释 |
| execution order | cell 实际被执行的顺序 |
| hidden state | 文档里看不到,但 runtime 中还残留的变量、import 和临时结果 |
| runtime state | 只存在于当前 session 中的变量、包和内存状态 |
| setup cell | 放在前部、集中处理 package import、选项和数据准备的 cell |
核心判断标准:把笔记本整理成可重新执行的记录¶
- 能说明为什么 notebook 要整理成可重新执行的学习记录。
- 能说明 execution order 和 hidden state 为什么会让 notebook 结果变得混乱。
- 能说明为什么环境、package 和数据准备 cell 应该放在 notebook 前部。
- 能说明在 Colab 共享中,notebook 内容和 runtime 状态是不同的东西。
- 能说明在什么时点,notebook 中验证过的代码应该拆分成函数和 script。
三个标准¶
| 标准 | 为什么重要 | 本节需要达到的理解程度 |
|---|---|---|
| 好的 notebook 到底哪里不同 | 它让你把文档质量和可重跑性一起看 | 阅读流程和执行流程必须一起被整理 |
| 为什么必须关心 cell 顺序 | 它把 notebook 的 hidden state 问题和文档结构本身连接起来 | 理解 notebook 虽然看起来像文档,但实际上也是真正的执行记录 |
| 它之后会通向什么 | 它让你提前看见“记录型 notebook”和“可复用代码”之间的边界 | 整理好的 notebook 会成为 script 和项目代码的起点 |
Notebook 既是文档,也是执行记录¶
Jupyter Notebook 文件是带有 .ipynb 扩展名的 JSON 文档。nbformat 文档解释说,notebook 包含 cell 列表和 metadata,而每个 cell 可以带有 input 和 output。Jupyter 架构文档也把 notebook 解释成同时存储 code、output 和 markdown notes 的文档。
这里把这种结构理解成下面这样。
flowchart TB
A["notebook 文件<br/>.ipynb 文档"]
B["Markdown 单元<br/>问题与说明"]
C["代码单元<br/>Python 代码"]
D["输出<br/>数值、表格、图表与错误"]
E["运行时状态<br/>变量与已导入包"]
A --> B
A --> C
C --> D
C -. "运行时创建" .-> E
这里重要的是:保存在文件里的内容,和执行中的状态,并不是同一回事。
Notebook 文件里可以留下 code 和一部分 output。但变量、import 过的 package、临时文件和内存状态都活在 runtime 里。一旦 runtime 被重启,这些状态就可能消失。
所以,仅仅保存 notebook 还不够。还必须检查这个 notebook 以后是否真的还能重新执行。
做出一个能从上到下运行的流程¶
好的学习型 notebook,应该能从上到下被阅读,也能从上到下被执行。
下面这个流程可以作为默认标准。
flowchart TB
A["1. 目的<br/>这个 notebook 要检查什么?"]
B["2. 设置<br/>导入与选项"]
C["3. 准备数据<br/>小示例或文件加载"]
D["4. 计算<br/>一次运行一个想法"]
E["5. 输出<br/>检查数值、表格和图表"]
F["6. 解释<br/>什么变了,怎么变的?"]
G["7. 下一步<br/>接下来试什么?"]
A --> B --> C --> D --> E --> F --> G
这种结构不是形式主义,而是思考顺序。
先写你想确认什么。然后加载需要的 package、准备数据、运行计算。看到结果后,再写解释。
如果这个顺序崩掉了,那么以后再打开 notebook 时,就很容易失去:为什么要做这个计算?、用了什么数据?、这个结果是什么意思?
第一格先写目的¶
在 notebook 前部,应先把目的写在 code 之前。
例如可以这样开始:This notebook calculates the mean and variance of a small score dataset and checks how the center and spread of data differ.
这一句话到后来会变得非常重要。因为 notebook 一旦不断增加 cell,就会很快变长。没有目的时,实验会散掉,结果也会变得不清楚,不知道到底在说明什么。
在目的 cell 里,简短写下下面这些内容。
| 项目 | 为什么要写 |
|---|---|
| 要检查的问题 | 防止实验散掉 |
| 使用的数据 | 明确结果的范围 |
| 预期输出 | 定义到底该看什么 |
| 不处理什么 | 防止 notebook 过度膨胀 |
Notebook 其实和 Section 很像。一个 notebook 最好也尽量只围绕一个中心问题。
把 package 和 setting 集中放在前面¶
如果 import 散落在 notebook 中间,之后重新运行时,就很难找到到底需要哪些 package。
一个好的习惯,是在前面放一个 setup cell。
问题场景:你想在 notebook 前部一眼看出到底用了哪些 package。 输入(input):numpy、pandas、matplotlib 的 import 代码。 期望输出(output):虽然没有输出,但后面 cell 需要的 package 名字已经准备好。 要确认的概念:看到把 import 集中在前部,会让 notebook 重跑和 dependency 检查都更容易。
这个 cell 告诉你:这个 notebook 到底用什么工具?
在 Colab 中,安装 package 的 cell 也要放在前面。
问题场景:你想让收到共享 Colab notebook 的人先安装需要的 package。 输入(input):一个 %pip 命令,用于安装 numpy、pandas、matplotlib。 期望输出(output):这些 package 会被安装到当前 kernel 里。 要确认的概念:看到把 package 安装 cell 放在前面,会让别人更容易准备出类似 runtime。
在 Colab 或 Jupyter 里,你也常会看到 !pip install ...。! 表示在 notebook cell 中执行 shell 命令。但对 Python package 安装来说,像 %pip 这样的 IPython magic command 往往和当前正在运行的 kernel 更匹配,因此这里优先介绍 %pip。
此处重要的不是安装方法本身,而是 package 安装与 import 必须集中在前面,这样别人才能明白 notebook 重跑需要做哪些准备。
保留清晰的数据准备 cell¶
Notebook 练习失败的常见原因之一,是文件路径。
在本地 PC 上,下面这个路径可能存在。
问题场景:你想通过最简单的字符串例子,看懂为什么同一个 notebook 在不同执行环境里会有不同文件路径。 输入(input):一个基于本地项目的 CSV 文件路径字符串。 期望输出(output):虽然没有输出,但代码预期的文件位置变得明确。 要确认的概念:看到可重跑 notebook 必须在代码里明确留下文件路径和数据位置。
但在 Colab 里,同样的文件可能不存在。路径会因为你是上传文件、连接 Google Drive,还是从 GitHub 下载,而发生变化。
所以在数据准备 cell 中,下面几种情况至少有一种应当明确。
| 情况 | notebook 里要留下什么 |
|---|---|
| 小型示例数据 | 直接在代码里创建 |
| 本地文件 | 写出文件位置和文件夹结构 |
| Colab 上传 | 写明需要上传 |
| Drive 文件 | 写明 Drive 连接与权限条件 |
| 从网络下载的文件 | 写明下载来源和确认日期 |
在这里,如果可能,更推荐直接把小型示例数据写进代码。
问题场景:为了不被文件路径问题打断、只专注于概念本身,你把一个小型示例数据直接写进代码里。 输入(input):一个包含五个分数的列表。 期望输出(output):虽然没有输出,但后续 cell 立即拥有要用的数据。 要确认的概念:看到在学习初期,比起文件型设置,代码里的小数据更有利于重跑和理解。
这对真实项目可能还不够,但对概念学习很好。它能让你把注意力放在平均值、方差、样本、误差这些概念上,而不是文件问题上。
不只留下 output,也要留下解释¶
Notebook 可以保存 output。但如果只留下 output,学习记录仍然不够。
例如,假设 notebook 里只留下了下面这个 output。
问题场景:你想看看,为什么只保存一个数字的 output,后来会很难解释。 输入(input):一个作为 cell output 留下来的单个数字 67.3。 期望输出(output):只剩下一个脱离上下文的数字,不知道它是平均值还是损失值。 要确认的概念:看到 output 不能只留下数值,而要在下面配上解释。
以后再看时,很难知道这个数字到底是 mean、accuracy 还是 loss。
所以应该在 output 正下方附上一句短解释,例如:The mean is 67.3. But because a low value like 45 is included, the whole distribution is hard to explain through the mean alone.
这一句话会改变学习记录的质量。Notebook 不应该只是堆放代码的文件,而应该是解释计算结果的记录。
cell 执行顺序会改变结果¶
Notebook 可以自由执行 cell。这个优点同时也是风险。
想象下面这种情况。
问题场景:想通过输出来比较,同一个变量被再次赋值时,runtime 当前记住的值会怎样变化。 输入(input):先把 learning_rate 设为 0.1,再设为 0.01。 期望输出(output):第一次保存的值,以及后来被覆盖后的值。 要确认的概念:notebook 的变量状态由最后执行的 cell 决定,而不只由文档中看到的顺序决定。
文档里从上到下能看到两个值,但在实际 runtime 中,会留下最近执行的那个值。如果你先执行下面那个 cell,再执行上面的 cell,结果又会再次变化。
所以,一个重要 notebook 应按下面方式检查。
- 重启 runtime。
- 从第一个 cell 到最后一个 cell 按顺序执行。
- 检查是否有报错的 cell。
- 检查 output 是否和 explanation 对得上。
- 清理不必要的临时 cell。
经历这个过程后,notebook 才更接近 可以重新执行的记录,而不只是 在我的电脑上偶然跑通一次的记录。
如果把这个检查顺序再写短一点:
| 检查步骤 | 为什么需要 |
|---|---|
| 重启 runtime | 为了清掉 hidden state |
| 从上到下执行 | 为了让文档顺序和执行顺序一致 |
| 检查错误 | 为了确认没有漏掉必要的 cell |
| 检查 output 和 explanation | 为了确认结果和解释一致 |
| 清理不必要的 cell | 为了不让可重跑记录变得模糊 |
固定 randomness,或至少解释它¶
在 AI 与统计练习里,random 元素经常出现。抽样、打乱数据、设定模型初值时,结果都可能发生变化。
在这里,仅仅说明“这里存在 randomness”,就已经是一个好的开始。
问题场景:你想看一个例子,通过固定 seed 来让同样的随机抽样可以再次确认。 输入(input):一个 seed 为 42 的随机生成器,以及从五个值里抽三个值的代码。 期望输出(output):一个以可复现方式输出的样本列表。 要确认的概念:看到在带有随机性的练习中,留下 seed 会让同样结果流更容易再次检查。
这里的 seed 可以看成重新制造同样随机流的起点值。不是每一个 notebook 都必须固定 seed,但如果你想再次看到同样结果,留下 seed 是好的做法。
一个要注意的点是:seed 并不能解决所有 reproducibility 问题。结果仍然可能因为 package 版本、执行环境、硬件或并行处理方式而不同。本节不深入展开这些细节。
在 Colab 共享中,notebook 共享和 runtime 共享不是一回事¶
Colab FAQ 说明,共享 notebook 时,text、code、output、comments 等 notebook 内容可以共享,但 virtual machine、runtime 文件和已安装的库状态不会一起共享。
因此,共享 Colab notebook 时,应检查下面这些点。
| 要检查什么 | 原因 |
|---|---|
| 是否有安装所需 package 的 cell | 对方的 runtime 中可能没装这些 package |
| 是否有准备数据文件的方法 | 我 runtime 中的文件不一定会被共享 |
| 是否需要 Drive 文件权限 | 对方可能访问不到我的私人 Drive 文件 |
| 是否能从上到下运行 | 这是在确认它不依赖 hidden state |
| output 是否已经过时 | 已保存 output 可能和当前代码的结果不同 |
这一点对本书里的示例 notebook 也很重要。读者打开链接时,不应该只看到代码,还应该知道什么必须先运行。
什么时候该从 notebook 迁移到 script¶
从 notebook 开始的代码,随着时间推移会变长。到某个时点,把它迁移到 .py script 会更合适。
当下面这些信号出现时,就应该考虑分离。
| 信号 | 含义 |
|---|---|
| 同样的代码在多个 cell 里重复出现 | 它可以被打包成函数 |
| cell 顺序经常打乱 | script 的执行顺序可能更安全 |
| 每次都要做同样的预处理 | 它可以搬进单独的函数或 module |
| 其他 notebook 里也会用到同样的代码 | 可能需要一个公共 .py 文件 |
| 需要自动执行 | script 比 notebook 更自然 |
流程可以这样看。
flowchart TD
A["notebook 探索<br/>尝试小想法"]
B["函数<br/>为重复逻辑命名"]
C["脚本文件<br/>运行相同步骤"]
D["模块<br/>从 notebook 中复用"]
A --> B --> C --> D
这里并不要求你一开始就建立 package 结构。先在 notebook 中理解,等重复代码出现后再打包成函数,等复用真正变重要时再拆分成文件。
学习型 notebook 的最小模板¶
制作学习型 notebook 时,可以把下面这个流程当成默认模板。
| 顺序 | cell 角色 | 例子 |
|---|---|---|
| 1 | purpose | 这个 notebook 要确认的问题 |
| 2 | environment | package 安装、import、版本检查 |
| 3 | data | 小型示例数据或文件路径 |
| 4 | calculation | 一次只运行一个概念 |
| 5 | output | 数字、表、图、错误信息 |
| 6 | interpretation | 结果意味着什么 |
| 7 | summary | 学到了什么、下一个问题是什么 |
这个模板不是形式主义,而是一个检查表。随着 notebook 变长,你要不断检查 purpose、environment、data、calculation、output、interpretation 是否都还在。
在进入 Part 2 下一章之前,只需要检查三件事。Notebook 能不能从上到下重新运行?Package、data 和 output explanation 是否都留在前部附近?重复代码是否已经准备好将来迁移到函数和 .py 文件?只要这个标准成立,后面的 Chapter 11 到 14 就会继续读成:在整理好的 notebook 中计算数组、读表、检查图、用 Git 留下记录,而不是在继续背新工具名称。
换句话说,P2-10 的目标并不是把 notebook 完全学透,而是建立一个标准,让 Part 3 之前的计算记录能以可重跑的形式留下来。
通过案例来看¶
案例 1. 今天能跑,明天却不能跑的 notebook¶
假设一个学习者在 Colab 中做数据预处理练习,cell 跑得很随意。中间执行了文件上传 cell,又在另一个 cell 里改了变量名,最后图也画出来了。那一天,文档看起来像是完成了。
但第二天重新打开 runtime,再从上到下执行时,文件可能不存在,后面的 cell 可能引用了前面并不存在的变量,而保存的 output 也可能已经和当前代码不一致。人会觉得:昨天还能跑,为什么现在不行? 但实际上,notebook 只是被留下成了 可读文档,却没有被整理成 可重跑记录。
为了减少这个问题,应把 purpose、package 安装、import 和 data preparation 集中放在前面,把 calculation 和 interpretation 按顺序排好,最后再重启 runtime,从头到尾重新执行。只有当同样结果再次出现时,这个 notebook 才会更接近一个可复现记录,而不只是一个偶然跑通的实验。
这个案例展示了整理 notebook 的核心。可复现性不是为了做出 漂亮文档,而是为了减少 hidden state,并且让另一天的人仍然能沿着同样流程再次确认。
检查清单¶
- 能不能把好的 notebook 解释成
可重跑的记录? - 能不能说明 hidden state 为什么会成为问题?
- 能不能说明为什么 setup cell 和 data-preparation cell 应该放在 notebook 前部?
- 能不能说明为什么需要重启 runtime 后再从上到下执行?
- notebook 前部有没有 purpose 和 scope?
- 所需的 import 和 package 安装 cell 是否都在前面?
- 是否解释了 data file 从哪里来?
- 从上到下重跑时会不会报错?
- output 下方有没有留下解释?
- 如果存在 randomness,是否说明了 seed 或波动可能性?
- 在 Colab 共享时,是否检查了 file、package 和 permission 问题?
- 是否有必要把重复代码拆成函数或
.py文件?
来源与参考资料¶
- Project Jupyter, Architecture, Jupyter Documentation 4.1.1 alpha,确认日期:2026-07-20。用于确认 notebook document 会把 code、output 和 markdown notes 一起保存。
- Project Jupyter, The Jupyter Notebook Format, nbformat 5.10 documentation,确认日期:2026-07-20。用于确认
.ipynb文件是包含 cell list、metadata、cell inputs 与 outputs 的 JSON-based document。 - Google, Google Colab FAQ, Google Colab,确认日期:2026-07-20。作为说明 Colab 共享时 notebook 内容与 runtime state 是分开的依据。