首页/新闻资讯/正文详情

Jupyter 笔记本新鲜度检查:用 freeze-check 守住 _quarto.yml 与 ipynb 的同步

发布时间:2026/9/26 10:35:25 来源:云帆数科 栏目:资讯中心
Jupyter 笔记本新鲜度检查:用 freeze-check 守住 _quarto.yml 与 ipynb 的同步
1. 当 Quarto 渲染出来的图还是上周的你有没有遇到过这种情况明明昨天刚在 Jupyter 里改完数据清洗逻辑重新跑了一遍 notebook结果quarto render出来的 HTML 里图表还是上周那版。翻回.ipynb一看输出确实更新了但_quarto.yml里注册的 notebook 路径和实际执行状态对不上Quarto 直接用了_freeze/里的旧缓存。这不是 Quarto 的 bug而是「笔记本新鲜度」这件事本身没有自动守门人。_quarto.yml只负责声明哪些 notebook 参与渲染它不关心你的.ipynb最后一次执行是什么时候、输出是不是比源码旧、_freeze缓存是不是已经过期。于是就有了一个很典型的脱节场景源码改了、输出没重跑、缓存还在渲染结果看起来正常实际是旧数据。freeze-check就是用来堵这个口子的。它读取_quarto.yml里manuscript.notebooks注册的路径逐个检查.ipynb是否有输出、最后修改时间、输出时效性、以及_freeze/notebooks/name/缓存状态最后给出一张新鲜度汇总表。适合用 Jupyter 做分析、用 Quarto 发布报告或论文的开发者尤其是那种「渲染前心里没底、不知道哪个 notebook 该重跑」的日常流程。下面我会先讲清楚它检查的四个维度再给一份可复制的配置骨架和检查命令然后完整演示一次「改 ipynb → 发现 stale → 重执行 → 验证同步」的动作。全程不需要你手动比对时间戳。2. 把 freeze-check 接进 Quarto 项目2.1 它到底检查什么freeze-check的核心不是简单比文件修改时间而是四个维度一起看维度检查内容判定意义Has outputs打开.ipynbJSON看 code cell 的outputs数组是否非空没输出说明根本没执行过Last modified.ipynb文件本身的修改时间戳源码是否被改过Outputs agecell metadata 里的执行时间戳如果有输出是什么时候生成的Freeze cache_freeze/notebooks/name/是否存在且含缓存Quarto 会不会直接用缓存四个维度组合出四种状态Current有输出且源码没比输出新、Stale有输出但源码改得比输出晚、Unexecuted任何 code cell 都没输出、Freeze only没 cell 输出但有_freeze缓存Quarto 会走缓存。关键点在于Stale和Freeze only这两种最容易被忽略。前者渲染出来是旧结果后者渲染出来可能连执行都跳过。2.2 前置准备项目结构与依赖假设你的 Quarto 项目长这样my-report/ ├── _quarto.yml ├── _freeze/ │ └── notebooks/ │ ├── notebook-01/ │ └── notebook-02/ ├── notebooks/ │ ├── notebook-01.ipynb │ └── notebook-02.ipynb └── index.qmd_quarto.yml里注册 notebook 的部分通常是这样project: type: book manuscript: notebooks: - notebooks/notebook-01.ipynb - notebooks/notebook-02.ipynbfreeze-check只认manuscript.notebooks这个列表路径写错或漏注册它就会报No notebooks found in _quarto.yml或者把文件标成 missing。所以第一步永远是确认这个列表和磁盘上的.ipynb一一对应。如果你还没配好模型侧的调用环境可以先把 API Key 准备好后面做自动化检查脚本时会用到。入口在 API Keys接入方式看 接入文档。这一步不是必须的纯本地检查也能跑但如果你想把检查结果自动汇总成报告有个稳定的模型接口会省事很多。3. 可复制的配置骨架与检查命令3.1 配置骨架freeze-check本身是一个检查技能落地到项目里我建议放一个scripts/freeze_check.py把读取_quarto.yml、遍历 notebook、判定状态这三步写死。骨架如下import json import os import time from pathlib import Path import yaml QUARTO_YML Path(_quarto.yml) FREEZE_DIR Path(_freeze/notebooks) def load_notebooks(): with open(QUARTO_YML, r, encodingutf-8) as f: cfg yaml.safe_load(f) return cfg.get(manuscript, {}).get(notebooks, []) def has_outputs(nb_path: Path) - bool: with open(nb_path, r, encodingutf-8) as f: nb json.load(f) for cell in nb.get(cells, []): if cell.get(cell_type) code and cell.get(outputs): return True return False def outputs_age(nb_path: Path): with open(nb_path, r, encodingutf-8) as f: nb json.load(f) stamps [] for cell in nb.get(cells, []): meta cell.get(metadata, {}) ts meta.get(execution, {}).get(iopub.execute_input) if ts: stamps.append(ts) return max(stamps) if stamps else None def freeze_exists(nb_path: Path) - bool: name nb_path.stem cache FREEZE_DIR / name return cache.exists() and any(cache.iterdir()) def check_one(nb_path: Path): if not nb_path.exists(): return Missing, None mtime nb_path.stat().st_mtime has_out has_outputs(nb_path) age outputs_age(nb_path) frozen freeze_exists(nb_path) if not has_out and frozen: return Freeze only, mtime if not has_out: return Unexecuted, mtime if age and mtime age: return Stale, mtime return Current, mtime def main(): notebooks load_notebooks() if not notebooks: print(No notebooks found in _quarto.yml) return print(f{Notebook:28}{Has Outputs:14}{Last Modified:22}{Status}) print(- * 78) for rel in notebooks: nb Path(rel) status, mtime check_one(nb) has_out Yes if nb.exists() and has_outputs(nb) else No ts time.strftime(%Y-%m-%d %H:%M, time.localtime(mtime)) if mtime else - print(f{nb.name:28}{has_out:14}{ts:22}{status}) if __name__ __main__: main()这段代码就是freeze-check的本地实现逻辑和前面表格里的四维度完全对应。outputs_age读的是 cell metadata 里的执行时间戳不是所有内核都会写所以判定Stale时如果拿不到 age会退化成只看mtime和是否有输出。3.2 运行检查在项目根目录执行python scripts/freeze_check.py输出会是一张表Notebook Has Outputs Last Modified Status ------------------------------------------------------------------------------ notebook-01.ipynb Yes 2026-02-28 14:30 Current notebook-02.ipynb Yes 2026-03-01 09:15 Stale notebook-03.ipynb No 2026-02-25 11:00 Unexecuted如果出现Stale或Unexecuted脚本会提示你重执行。Quarto 项目里对应的动作是quarto render --execute或者只重跑 notebookjupyter nbconvert --to notebook --execute notebooks/notebook-02.ipynb \ --output notebooks/notebook-02.ipynb注意--execute会重新生成输出并更新 cell metadata 里的时间戳这样下一次freeze-check才会把它判成Current。4. 一次完整的同步验证4.1 改一个 notebook打开notebooks/notebook-02.ipynb随便改一个 cell比如把df.groupby(region).sum()改成df.groupby(region).mean()保存。此时.ipynb的mtime更新了但 cell 输出还是旧的。跑一次检查python scripts/freeze_check.py你会看到notebook-02.ipynb的状态从Current变成Stale因为mtime outputs_age。这就是脱节的信号源码新、输出旧。4.2 重执行并验证执行jupyter nbconvert --to notebook --execute notebooks/notebook-02.ipynb \ --output notebooks/notebook-02.ipynb再跑一次freeze_check.pynotebook-02.ipynb Yes 2026-03-01 09:40 Current状态回到Current说明输出已经跟上源码。这时候再quarto render渲染结果就是新的。如果你在检查过程中想确认某个 notebook 的执行逻辑对不对可以把关键 cell 贴到 模型对话 里让模型帮你核对尤其是涉及数据聚合、时间窗口这类容易写错的地方。4.3 把检查接进日常流程最省事的做法是在quarto render之前挂一个 pre-render 钩子。Quarto 支持_quarto.yml里配project: pre-renderproject: type: book pre-render: python scripts/freeze_check.py这样每次渲染前都会先跑一遍新鲜度检查有Stale或Unexecuted就直接暴露出来不会等到 HTML 出来才发现图是旧的。如果你在做长期编码或 Agent 类的自动化流程可以把检查脚本和重执行命令串成一个任务交给 Coding Plan 里的工作流去跑省得每次手动敲。5. 本篇常见错排查5.1 报 No notebooks found in _quarto.yml说明manuscript.notebooks是空的或者键名写错了。检查_quarto.yml里是不是写成了notebooks:而不是manuscript: notebooks:。Quarto 的 book 项目里 notebook 注册必须在manuscript下写成顶层notebooks不会被识别。5.2 某个 notebook 被标成 Missingfreeze-check按_quarto.yml里的相对路径去找文件路径是相对项目根目录的。如果你在子目录里跑脚本Path(rel)就会解析错。解决办法是脚本里统一用项目根目录做基准ROOT Path(__file__).resolve().parent.parent nb ROOT / rel5.3 状态一直是 Stale重执行也没用大概率是 cell metadata 里没有执行时间戳outputs_age返回None判定逻辑退化成只看mtime。有些内核比如部分老版本 ipykernel不写iopub.execute_input。这时候可以改用nbconvert的--ExecutePreprocessor.record_timingTrue强制记录jupyter nbconvert --to notebook --execute notebooks/notebook-02.ipynb \ --output notebooks/notebook-02.ipynb \ --ExecutePreprocessor.record_timingTrue5.4 Freeze only 状态怎么处理Freeze only表示没有 cell 输出但有_freeze缓存Quarto 渲染时会直接用缓存。如果你希望渲染时真正执行 notebook需要先清掉缓存再重执行rm -rf _freeze/notebooks/notebook-03 jupyter nbconvert --to notebook --execute notebooks/notebook-03.ipynb \ --output notebooks/notebook-03.ipynb清缓存这一步要谨慎确认缓存不是唯一输出来源再删。5.5 检查脚本报 YAML 解析错_quarto.yml里如果有 tab 缩进或者中文冒号yaml.safe_load会直接抛异常。Quarto 的 YAML 必须用空格缩进冒号用英文。可以用python -c import yaml; yaml.safe_load(open(_quarto.yml))单独验证一下配置文件本身能不能解析。6. 把新鲜度检查变成渲染前的默认动作freeze-check的价值不在于它多复杂而在于它把「哪个 notebook 该重跑」这件事从人脑记忆变成了可执行的检查。四维度判定里Stale和Freeze only是最容易骗过眼睛的两种状态前者渲染出旧结果后者直接跳过执行。把检查脚本挂到pre-render每次渲染前自动跑一遍基本就能告别「图是上周的」这类问题。如果你想把检查结果自动汇总、或者在多项目之间统一管理可以走 控制台 配一套 API 调用把freeze_check.py的输出结构化后发给模型做摘要。接入细节在 接入文档 里有完整说明Key 在 API Keys 页面生成。纯本地检查不需要这些但一旦你想把新鲜度检查纳入 CI 或者多人协作流程有个稳定的接口会方便很多。

相关推荐

跟 Trae 只说了一句话就仿了一个「兽音译者」小程序出来:TaoToken 统一 Key 接入与配置骨架
跟 Trae 只说了一句话就仿了一个「兽音译者」小程序出来:TaoToken 统一 Key 接入与配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 10:35:25

2026年4月OpenClaw腾讯云部署:5分钟安装与百炼APIKey配置避坑指南
2026年4月OpenClaw腾讯云部署:5分钟安装与百炼APIKey配置避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 10:35:25

superpowers-zh × DeepSeek Harness(dsh)安装实战指南:一条 npx 命令装好 20 个技能,四条官方路径全部可溯源
superpowers-zh × DeepSeek Harness(dsh)安装实战指南:一条 npx 命令装好 20 个技能,四条官方路径全部可溯源

AI 技能AI 插件人工智能开发工具 【免费下载链接】superpowers-zh 🦸 AI 编程超能力 中文增强版 — superpowers(250k ⭐)完整汉化 4 个中国原创 skills,让 Claude Code / Copilot CLI / Hermes Agent / Cursor / Windsurf / Ki… · 2026/9/26 10:35:25

数智码力:Python字符串处理大全!日常文本操作一键搞定
数智码力:Python字符串处理大全!日常文本操作一键搞定

文本处理属于最频繁出现的实践应用场景之一, 在日常办公活动里进行文字整理、数据清理、内容解析以及批量修改文件内容等事务时, 都是无法离开字符串操作环节的。很多初学者在面对文本处理工作场合,仅仅能够使用基础的拼接和切片方法, 一旦遭遇复杂的替换、分割、去除空格、格式… · 2026/9/26 11:08:11

我的编程学习启程 | 第一篇博客
我的编程学习启程 | 第一篇博客

大家好,这是我的第一篇技术博客,很高兴在这里记录我的编程成长之路。自我介绍我是一名计算机相关专业的学生,目前正在学习C语言,初步接触程序逻辑、基础语法。在学习过程中,我感受到代码解决问题的能力,也发… · 2026/9/26 11:08:11

Java 排序算法详细教学:从冒泡排序到快速排序
Java 排序算法详细教学:从冒泡排序到快速排序

1. 为什么需要掌握排序算法排序是程序开发中最常见的基础操作之一。无论是给商品列表按价格排序、给用户按注册时间排序,还是给搜索结果按相关度排序,背后都离不开排序算法。对于 Java 开发者来说,掌握排序算法不仅能帮助你理解 Arrays.sort … · 2026/9/26 11:08:11

省下千元订阅费!用 TaoToken 统一 Key 给 Cline 配置自主任务 AI 智能体,codex 平替实测
省下千元订阅费!用 TaoToken 统一 Key 给 Cline 配置自主任务 AI 智能体,codex 平替实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 11:08:11

C++代码实现MATLAB中的d2c函数功能
C++代码实现MATLAB中的d2c函数功能

// d2c.cpp // 完全自包含的 MATLAB d2c (ZOH) 实现 // 编译: g -stdc17 -O2 d2c.cpp -o d2c#include <iostream> #include <iomanip> #include <complex> #include <vector> #include <cmath> #include <stdexcept> #include <string&… · 2026/9/26 11:08:11

Atlas 300V部署YOLOv5全指南:从模型转换到性能调优
Atlas 300V部署YOLOv5全指南:从模型转换到性能调优

最近在做一个边缘AI推理项目&#xff0c;客户指定要在Atlas平台上跑YOLOv5目标检测&#xff0c;整个过程中从选型、部署到调优踩了不少坑。今天把整个项目完整拆一遍&#xff0c;从Atlas 300V 24G这块卡到底是不是运算加速卡开始&#xff0c;到最终YOLO模型成功上卡推理&#x… · 2026/9/26 11:08:04

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介&#xff1a;万常选版《数据库原理与设计》课后习题答案资源&#xff0c;覆盖第2至6章及第9章&#xff0c;适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件&#xff0c;含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故&#xff0c;是很多团队绕不过去的坎。线上环境里&#xff0c;服务端明明已经上线了新版接口&#xff0c;老的移动端还在照着旧文档传参数。请求一到网关&#xff0c;校验直接拒绝&#xff0c;用户操作失败&#xff0c;客服群炸了锅&#xff0c;开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码