Jupyter Notebook 是一个网页界面,把代码分成一格一格的,写一格跑一格,结果就留在下面。 它存下来的是一个 JSON 文件(.ipynb),里面只有「格子内容」和「最后一次的输出」, 不存「你按了什么顺序跑」。这是所有怪现象的根源。 这一节的 Demo 真写了一个三格的 notebook 并读回来: 588 字节、39 行、两个 code 格一个 markdown 格;再演示同一份代码换顺序跑出两个结果。
上一节装了框架,这一节是写代码的地方。 书里第 3-2 节专门讲它,因为后面每一步实验都在 Notebook 里做。 这一节和算法无关,但踩过坑的人都知道:notebook 的隐状态问题浪费的时间,比学一个算法还多。
一、装与起
pip install notebook,然后在一个目录里敲 jupyter notebook,浏览器会自动打开一个页面,
地址是 localhost 加一个端口。
它是个本地服务,跑在你自己的机器上,关掉命令行窗口服务就停了。
所以别把它当成「网页版 Python」,它没有云端。
书里还提了 JupyterLab,那是新一代界面,功能更多(能开终端、能拖文件)。
两者读的是同一批 .ipynb 文件,用哪个都行。
二、格子有两种
Code 格写代码,Shift+Enter 运行,结果显示在格子下面。 Markdown 格写说明,同样 Shift+Enter 渲染成标题、列表、公式。 在命令模式(格子左边蓝色)下按 A 在上面插一格、B 在下面插一格、DD 删掉当前格。 这些快捷键用熟了比点菜单快得多。 输出是跟着格子存的。所以你把 notebook 发给别人,别人能看到你的输出, 但看不到你是怎么跑出来的——这就是下一节要讲的坑。
三、它就是一个 JSON
把 .ipynb 用文本编辑器打开,会看到一个 JSON:顶层四个键——cells、metadata、nbformat、
nbformat_minor。
cells 是个列表,每一项是一格:cell_type 说明是 code 还是 markdown,
source 是内容(按行拆成字符串列表),code 格还多出 outputs 和 execution_count。
知道这件事有三个好处:能在 git 里 diff(它是纯文本)、能用脚本批量改(比如清空所有输出)、
能自己程序化生成 notebook。
四、执行顺序的坑
Notebook 的每个格子共享同一个 Python 进程,变量在格子之间是活的。这很方便,也很危险。
典型场景:第 1 格写 a = 1,第 2 格写 a = a + 1。按 1→2 跑一遍,a 是 2;按 2→1 跑一遍,a 是 1。
同一份文件,两个结果。
更隐蔽的是循环引用:你在第 5 格改了第 2 格用过的一个函数,但只重跑了第 5 格,
于是后面的结果一半用旧函数一半用新函数。这种错误看代码看不出来,只有重跑才能发现。
所以规矩只有一条:交结果之前,Restart Kernel and Run All。从头按顺序跑一遍,跑得通、结果对,
才算数。
五、两个实用技巧
%matplotlib inline:让画出来的图直接嵌在输出里,而不是弹一个新窗口。
新版 notebook 默认就是内嵌,不加也多半没问题。
%%time:放在格子第一行,跑完会告诉你这一格花了多久。
测训练速度时很有用,比自己在代码里插 time 干净。
书里那个 POINT 讲的是 Mac 上的执行方式——Mac 上装完 Jupyter 可能需要python3 -m jupyter notebook,
因为 jupyter 这个命令可能没进 PATH。
六、动手 Demo:它是一个 JSON 文件
用代码生成一个最小 notebook(两格),存盘、读回来、数数它有几个 cell、多少行代码; 然后用 exec 模拟「同样的两格、换顺序跑」会发生什么。
import json
nb = {
"cells": [
{"cell_type": "markdown", "metadata": {},
"source": ["# 第一个 Notebook\n"]},
{"cell_type": "code", "execution_count": 1, "metadata": {},
"outputs": [], "source": ["a = 1\n"]},
],
"metadata": {"kernelspec": {"name": "python3"}},
"nbformat": 4,
"nbformat_minor": 5,
}
json.dump(nb, open("demo.ipynb", "w", encoding="utf-8"),
ensure_ascii=False, indent=1)
# 执行顺序:同一份代码,两种顺序两个结果
ns = {}
exec("a = 1", ns) # 第 1 格
exec("a = a + 1", ns) # 第 2 格
print(ns["a"]) # 2
实跑输出:
--------------------------------------------------------------------
使用 Jupyter Notebook:它是一个 JSON 文件
--------------------------------------------------------------------
写了一个三格的 notebook:588 字节,39 行。
它的顶层只有四个键:cells、metadata、nbformat、nbformat_minor
cell 类型 个数
--------------------
code 2
markdown 1
代码行合计 6 行。**整个 notebook 就是这样一个 JSON**——
所以它能在 git 里 diff、能被脚本读写、也能被 nbconvert 转成 html。
真正要小心的是「执行顺序」。同样两格代码,按不同顺序跑,结果不一样:
第 1 格:a = 1
第 2 格:a = 2
按 1 → 2 的顺序跑完,a = 2
(先跑第 2 格)a = 100
(再跑第 1 格)a = 1
按 2 → 1 的顺序跑完,a = 1
**同一个 notebook,两次跑出两个结果。**笔记本保存的是「格子」和「最后一次的输出」,
不保存「执行顺序」。所以看到别人的 notebook 结果对不上,第一件事是
Restart & Run All——从头按顺序跑一遍,才能确认结果是真的。
588 字节、39 行、2 个 code 格加 1 个 markdown 格。这就是一个 notebook 的全部——
没有任何隐藏状态存在文件里。
顶层四个键里,nbformat 和 nbformat_minor 是格式版本号(4 和 5)。
别人打不开你的 notebook,很多时候就是版本号对不上。
后半段那个实验是关键:按 1→2 的顺序跑,a 最后是 2;按 2→1 的顺序跑,a 最后是 1。
代码一个字没变,结果变了。
现实里更糟:你不会只跑两格,而是几十格来回跳。
所以看到「书上的代码我抄了但结果不对」,第一件事不是怀疑书,是 Restart & Run All。
30 秒实验
- 把生成出来的 .ipynb 用文本编辑器打开,找到
execution_count这个键,看它是什么。 - 在真实 notebook 里故意乱序跑一次,再 Restart & Run All,对比两次的输出。
- 用
jupyter nbconvert --to script demo.ipynb把它转成 .py,看转换成什么样。
常见误区
- 把 notebook 当成脚本用。它是探索工具,不是程序。 要交付、要定时跑、要给别人调用,就写成 .py。
- 不重启就一直往下跑。跑上几个小时之后,内存里堆着一堆早就删掉的东西, 结果谁也说不清是怎么来的。
- 把大数据集读进 notebook 变量然后保存。输出里嵌着图片和表格,文件能涨到几十兆, git 一提交就再也拉不动。
自测三问(点开对答案)
1. notebook 文件里存了什么、不存什么? —— 存了每个格子的内容和最后一次的输出,不存执行顺序、不存运行时内存。 所以「跑出这个结果的过程」是不可复现的。 2. 为什么会出现「同样的代码两次结果不同」? —— 因为所有格子共享一个进程,变量是活的。换一个执行顺序,同一个变量在不同时刻的值就不一样。 3. 交作业前必须做哪一步? —— Restart Kernel and Run All。从头按顺序跑一遍,确认在干净状态下结果成立。工具就绪,开始看数据。下一节是 NumPy——这一节要盯住的只有一件事:形状。
- ☐ (本节完,下一节 3.3 数值运算函式库 NumPy 的使用方法)