首页/Python 与数据实践/Jupyter 交互环境
Python 与数据实践 · 第 25 章

Jupyter 交互环境

第 22-24 章里,我们一直是"工程师"的姿态:配置构建、管理依赖、组织 workspace、上断点排查问题——所有努力都指向同一个目标,把代码当成产品,稳定地交到别人手里

约 11 分钟4,363 字17 个代码示例

第 22-24 章里,我们一直是"工程师"的姿态:配置构建、管理依赖、组织 workspace、上断点排查问题——所有努力都指向同一个目标,把代码当成产品,稳定地交到别人手里。

从本章开始,姿态要换了。接下来的七章(25-31)是课程的数据实践部分:NumPy、Pandas、Matplotlib、Seaborn——拿到数据,看清数据,把数据讲给别人听。而做数据的人和写工程的软件有一个根本差别:你事先不知道答案。数据里藏着什么规律、该不该去掉异常值、用哪种图更清楚,都要试过才知道。这种"边试探边前进"的工作,需要一件趁手的兵器——Jupyter notebook。


1. 从"写脚本"到"做实验"

先用你已经熟悉的方式,感受一下痛点。假设拿到一家小店一周的订单数据,想搞清楚周中周末的销售差异,你可能会写一个 analyze.py:

# analyze.py —— 脚本式写法
sales = [320, 285, 410, 368, 295, 180, 150]

total = sum(sales)
weekend = sales[5] + sales[6]
print(f"全周合计: {total}")
print(f"周末占比: {weekend / total:.1%}")

跑一次,看两行输出。然后呢?"占比好像有点低,先看看每天和前一天的环比?"——于是加几行,再跑一次;"要不算算去掉周末的日均?"——再加几行,再跑一次。每改一次就重跑一遍整个文件,前面的结果在终端里哗哗刷过去,你看不到也留不下。

数据探索的真相是:每一步都想回头看一眼——看上一步算出来的东西长什么样,再决定下一步往哪走。脚本"从头跑到尾"的模式和这个节奏天然拧巴。

notebook 解决的就是这件事。它把文档拆成一个个单元格(cell):想算什么,在 cell 里敲一行,立刻看到结果;结果不满意,改掉这个 cell 重跑就行,别的 cell 不受影响。代码、输出、图表、你随手写的备注,全部按顺序留在同一个文件里——与其说是"代码文件",不如说是实验记录本:等分析做完,这份记录本身就是可交付的报告。

顺带一提:Jupyter 这个名字来自它最初支持的三门语言 Julia、Python、R——如今它早就是数据科学领域事实上的标准工作台了。

2. 搭建环境:VSCode + uv

本课程全程用 VSCode 写代码,notebook 也不例外,只需要装一个扩展。

第一步:在 VSCode 扩展市场搜索并安装 Jupyter 扩展(Microsoft 出品)。

第二步:给数据实验建一个独立的工程。第 23 章介绍过 uv,这里直接用:

# 新建一个数据实验工程
uv init data-lab
cd data-lab

# 把 jupyter 装进开发依赖
uv add --dev jupyter

注意 --dev 这个细节:notebook 是你做分析时用的工具,不是程序跑起来必需的东西。第 22 章讲构建发布时说过,开发工具类依赖应该留在 dev 组里——将来这个工程如果要打包发布,jupyter 不会被带进去。

3. 跑通第一个 notebook

在工程里新建一个文件,后缀名改成 .ipynb(比如 explore.ipynb),VSCode 会立刻认出它是个 notebook。

第一次打开时,编辑器右上角会提示选择内核(kernel)——选工程虚拟环境(.venv)里那个 Python 就行。所谓内核,就是背后真正替你执行代码的那个 Python 进程,这个概念下一节细说。

notebook 由一格一格的 cell 组成,每格两种类型:

  • 代码 cell:写 Python,可以运行,运行结果直接显示在 cell 下方
  • Markdown cell:写标题、说明、结论,渲染成带格式的文字

试着建两个 cell:

[Markdown cell]  # 小店一周订单速览

[代码 cell]
week_sales = [320, 285, 410, 368, 295, 180, 150]
print(f"共 {len(week_sales)} 天,合计 {sum(week_sales)} 单")

运行方式:点 cell 左侧的 ▶ 按钮,或者快捷键 Shift+Enter(运行当前格并跳到下一格,最顺手的一个)。运行后 cell 左边会出现 In [1] 这样的序号——它是执行顺序的编号,先记住这个细节,下一节会用到。

最后看一眼 .ipynb 文件的本质:它就是个 JSON 文件,里面记录着所有 cell 的内容和上一次运行的输出。所以把 notebook 直接发给同事,对方打开就能看到你完整的分析过程和每一步的结果——"过程即交付物",这是脚本给不了的。

4. 内核与状态:变量都去哪儿了

notebook 最重要、也最容易踩坑的机制只有一个:同一个 notebook 的所有 cell,共享同一个内核进程。

这意味着:前面 cell 里定义过的变量,后面的 cell 可以直接用,不用重新定义。比如:

# cell 1:定义两家门店的日营收
north_income = 4300
south_income = 3700
# cell 2:直接引用上面的变量
print(north_income + south_income)   # 8000

cell 2 里既没有 import 也没有定义,但它能跑——因为 cell 1 执行时,变量已经留在了内核的内存里,随时可取。

方便的另一面是坑:cell 的实际执行顺序,可以和它在文件里的排列顺序不一样。看这个经典陷阱:

# cell A
total = 100
# cell B
total = total + 50
print(total)

先跑 A 再跑 B,输出 150,一切正常。但如果你连续运行 cell B 三次呢?第二次 total 已经是 150,输出 200;第三次输出 250。文件一个字没改,结果却变了——因为 notebook 的状态由执行历史决定,而不是由文件内容决定。乱序运行、反复运行某个 cell,都会让"你看到的代码"和"内核里的状态"悄悄脱节,等到结果复现不出来,debug 会非常痛苦。

三个习惯能帮你避开这个坑:

  1. 交付前重放一遍:用工具栏的 Restart Kernel(清空内存重新来)+ Run All,从零按顺序跑通,结果才是可信的
  2. 疑惑时看序号:cell 左边的 In [n],n 越大越是最近执行的;序号乱掉往往意味着执行顺序乱了
  3. 让文件顺序等于执行顺序:写 cell 时就保证从上往下读一遍即是正确跑法,把"初始化"类的 cell 放在最前面

5. 魔法命令:notebook 里的瑞士军刀

notebook 里除了 Python 代码,还能写一类特殊命令——魔法命令(magic command)。它们以 % 或 %% 开头,不是 Python 语法,而是 IPython 内核附送的便利功能。介绍几个数据工作里出场率最高的:

%who / %whos:现在内核里有哪些变量?

%who        # 列出所有变量的名字
%whos       # 更详细:名字、类型、值,排查"我的变量怎么不见了"专用

%timeit / %%timeit:这行代码跑多快?

%timeit sum(range(1_000_000))
# 输出形如:10.2 ms ± 0.3 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit 自动多次运行取平均(单次计时受偶然因素影响太大);两个百分号 %%timeit 作用于整个 cell。数据工作里"两种写法哪个快"是高频问题——下一章对比向量化运算和 for 循环时,它就是裁判。

%run:在 notebook 里跑一个脚本

%run prepare.py   # 执行外部脚本,脚本里定义的变量会直接进入当前内核

! 开头:Shell 逃逸——不离开 notebook 就能敲终端命令

!uv add numpy    # 装个库,不用切窗口
!ls              # 看看当前目录

! 后面跟的就是普通 shell 命令。下一章装 NumPy 时就直接用上它。

6. 课堂练习

新建一个 notebook,完成下面四个练习。每个练习都先预测输出,再运行验证。

练习一:跨单元格的接力

原样体会"内核共享变量":把下面两段代码放进两个不同的 cell。

# cell 1:两款饮品的单价
americano = 22
matcha_latte = 15
# cell 2:各点一杯,一共多少钱?
print(americano + matcha_latte)   # 先猜:______

再新建第三个 cell,不许回去重新定义变量:

# cell 3:外带三杯抹茶拿铁呢?
print(matcha_latte * 3)   # 先猜:______

练习二:执行顺序陷阱

同样两个 cell:

# cell A
score = 60
# cell B
score = score + 5
print(score)

任务:先跑 A 再跑 B,记下输出;然后连续再运行 B 两次,每次运行前先写下你的预测。最后 Restart Kernel,只按 A → B 的顺序重跑一遍,体会"重启后从零开始"意味着什么。

练习三:向内核要一份"变量清单"

在一个 cell 里定义三四个变量(随手什么都行),然后在下一个 cell 里运行 %who 和 %whos,对比两者输出的差别。再运行一次 Restart Kernel,看看 %who 还剩什么。

练习四:给 notebook 写"封面"

在最前面插入一个 Markdown cell,写上:

# 咖啡店经营速览

> 数据来源:虚构的练习数据。本 notebook 演示 notebook 的基本工作流。

体会一下:分析做到一半回头补说明、补结论,在任何位置插入文字,这正是"过程即文档"的含义。


实验台搭好了。但你会发现一个新问题:目前我们手里只有 Python 列表,算个均值要 sum(x) / len(x),两批数据对应相加得写 for 循环——数据一大,又慢又啰嗦。下一章,请出数据实践真正的主角:NumPy。


下一章:26 · NumPy 数组与向量化