Jupyter 交互环境
第 22-24 章里,我们一直是"工程师"的姿态:配置构建、管理依赖、组织 workspace、上断点排查问题——所有努力都指向同一个目标,把代码当成产品,稳定地交到别人手里
第 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 会非常痛苦。
三个习惯能帮你避开这个坑:
- 交付前重放一遍:用工具栏的 Restart Kernel(清空内存重新来)+ Run All,从零按顺序跑通,结果才是可信的
- 疑惑时看序号:cell 左边的
In [n],n 越大越是最近执行的;序号乱掉往往意味着执行顺序乱了 - 让文件顺序等于执行顺序:写 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。