1. 先把 Jupyter Notebook 到底是什么讲透
1.1 它不是"网页版 Python",而是能留住现场的实验台
很多人第一次接触 Jupyter Notebook,是被"网页版"这三个字骗进来的,以为它就是个跑在浏览器里的 Python 解释器。实际上它的本质是一个把代码、运行结果、文字说明、图表全部封存在同一个文件里的交互式计算环境。这个文件就是.ipynb,名字里的 ipynb 正是从 IPython Notebook 继承下来的历史包袱——2014 年项目从 IPython 里拆出来,改名 Jupyter,取的是 Julia、Python、R 三个名字的组合,但文件后缀没跟着改,一直留到今天。所以你在搜索框里输入 IPython Notebook,翻出来的资料大多是 2015 年前后的老帖,命令写法、配置项位置、扩展兼容性都和现在差得很远,这一点先记住,能省下大量试错时间。
它能做的事,用一句话概括:把你"敲一行命令、看一个结果、再决定下一步"的探索过程,完整地保存在一个可重复运行、可分享、可导出的文档里。做数据分析的人拿它清洗表格、画分布图;写 Python 爬虫的人拿它一段段调试请求和解析逻辑;学算法的人在里面手推 01 背包动态规划的口诀表、跑层次聚类的树状图;做教学的人把讲解文字和可执行代码写在一起,学生点一下就能看到结果。这些场景的共同点是过程比结果更重要,而传统的.py脚本只留下结果,中间那些"我试了三种写法"的痕迹全丢了。
适合读下去的人,大致是三类:刚装完 Python、准备找第一个练手环境的新手;用了一段时间但被"单元格执行没反应""打不开""ImportError"这些毛病反复折磨的熟手;以及想把 notebook 纳入正式工作流、但不确定怎么和 VS Code、nvim、Git 打配合的老手。三类人的关注点完全不同,我尽量分开讲,你按需跳读。
1.2 从 IPython 到 Jupyter,哪些概念一直没变
搞清楚几个老概念,很多报错信息你就能读懂。IPython 时代留下的东西,到今天还在用:
In [ ]和Out [ ]:执行计数器和输出缓存。这个方括号里的数字不是装饰,它是内核记录的执行序号,也是很多诡异 bug 的源头,后面第 3 章会专门拆。- 魔法命令:以
%开头的行魔法和%%开头的单元格魔法,比如%timeit、%matplotlib inline、%%writefile。这套东西是 IPython 发明的,Jupyter 全盘继承。 - 内核(Kernel):真正执行你代码的那个 Python 进程。前端(浏览器页面)和内核之间靠 ZeroMQ 通信,它们俩是两个独立的进程,这个事实解释了一大半的"卡住"和"没反应"。
ipykernel:让 Python 能当 Jupyter 内核的那个包。你装jupyter notebook的时候它会作为依赖被带进来。
.ipynb文件本身是个 JSON,结构上分成cells、metadata、nbformat几大块,每个 cell 里有cell_type、source、outputs、execution_count。输出结果是实实在在写进文件里的——这一点决定了它体积大、Git diff 难看、但也决定了你关掉浏览器再打开,图还在那儿。理解了文件结构,你后面遇到"notebook 打不开、提示 JSON 解析失败"就知道该去文本编辑器里翻尾部是不是被截断了。
提示:
.ipynb的 JSON 里如果混进了非法字符(常见于手改文件或者同步盘写入中断),Jupyter 会直接拒绝加载。遇到这种情况先备份,再用jupyter nbconvert --to notebook --nbformat 4 坏文件.ipynb尝试修复,比在网上找在线修复工具靠谱得多。
1.3 什么场景该用它,什么场景趁早换工具
我不太喜欢"Jupyter 万能"这种说法。它的优势是交互式探索和结果呈现,短板同样明显:
| 场景 | 用 Notebook | 换别的工具 |
|---|---|---|
| 数据清洗、特征探索、画图 | 非常合适 | — |
| 教学演示、算法过程可视化 | 非常合适 | — |
| 爬虫调试请求参数 | 合适 | — |
| 长期运行的定时任务 | 不合适 | 写成.py+ 系统计划任务 |
| 上千行的业务模块 | 不合适 | 拆成包,用 PyCharm / VS Code |
| 多人同时改同一个文件 | 不合适 | 拆模块,或改用 Jupytext 转.py |
| 需要严格版本控制的代码库 | 不合适 | Git 管.py,notebook 只做展示 |
有个判断标准特别好用:如果这个文件你三个月后还要回来改、还要给别人维护,那就别用 notebook 当主载体。notebook 适合当草稿纸和实验记录,成熟之后把稳定下来的函数抽到.py文件里,notebook 只留调用和展示。这是我踩过坑之后最想说的一句。
2. 安装与环境搭建:把第一道坑挡在门外
2.1 pip、conda、集成发行版,三条路怎么选
jupyter notebook 安装这个搜索词的热度一直很高,说明卡在第一步的人非常多。目前主流有三条路:
第一条,pip 直装。前提是你已经有一个能用的 Python(建议 3.9 以上,3.11 是目前兼容性比较舒服的一档)。命令很简单:
python -m pip install --upgrade pip python -m pip install notebook装完敲jupyter notebook就能起。优点是干净、可控;缺点是纯 pip 环境里科学计算相关的二进制包(numpy、scipy、pyzmq 这类)在某些平台需要自己解决编译依赖,Windows 上偶尔会撞见 DLL 相关报错。
第二条,conda / mamba。数据科学圈的老牌选择,二进制依赖由 conda 统一调度,装 numpy、pandas、matplotlib 时省心很多:
conda create -n nb311 python=3.11 conda activate nb311 conda install -c conda-forge jupyterlab notebook第三条,各种 Python 集成发行版。装完自带 Jupyter、numpy、pandas 一大套,适合完全不想折腾环境的新手。代价是环境体积大、包版本偏旧,日后想单独升级某个库容易互相牵扯。
我的建议很明确:如果你打算长期写 Python,走第二条或第一条 + 虚拟环境;如果只是临时跑个教学 demo,第三条也行,但别指望它陪你走很远。
注意:pip 和 conda 混用是 DLL 报错的头号元凶。同一个环境里,numpy 用 conda 装了、某个包又用 pip 拉了一个不同版本的 numpy 进来,运行时就可能加载到错误的
.dll/.so。选定一条路就尽量走到底,实在要用 pip 装 conda 里没有的包,装完跑一次pip check看有没有冲突。
2.2 虚拟环境与内核注册,这一步别偷懒
新人最常犯的错,是全局环境里堆了三十个包,然后 notebook 里import cv2报找不到模块,或者反过来,装了两个版本的 Python,notebook 用的内核根本不是你刚装包的那个。
正确姿势是每个项目一个虚拟环境,然后把环境注册成 Jupyter 内核:
# 建环境 python -m venv .venv # 激活(Windows) .venv\Scripts\activate # 激活(macOS / Linux) source .venv/bin/activate # 装内核和常用包 pip install ipykernel jupyterlab pandas matplotlib # 注册成内核,名字自己取,显示名建议带上 Python 版本 python -m ipykernel install --user --name=nb311 --display-name "Python 3.11 (nb311)" # 查看现有内核列表 jupyter kernelspec list--display-name里的版本号一定要写清楚。我见过太多人机器上有四个Python 3内核,名字一模一样,选错了就出现"明明装了 pandas 却说没有"的诡异现象。删除多余内核用jupyter kernelspec remove 内核名,比手动去找 kernels 目录文件干净。
另外一个细节:注册内核时记录的是那个 Python 解释器的绝对路径。如果你后来把虚拟环境目录重命名或者删掉了,notebook 里选这个内核就会启动失败,报No such file or directory或者内核一直处于"正在连接"状态。这时候回终端jupyter kernelspec list看一眼路径就明白了。
2.3 启动参数、配置文件与局域网访问
裸敲jupyter notebook会做三件事:启动服务、占用 8888 端口、自动打开浏览器。日常用没啥问题,但有几个参数值得记住:
# 不自动开浏览器,只打印访问地址 jupyter notebook --no-browser # 换端口,8888 被占了的时候特别有用 jupyter notebook --port 8899 # 指定工作目录,省得每次都 cd jupyter notebook --notebook-dir=/Users/me/work想固定下来,就生成配置文件:
jupyter notebook --generate-config它会在用户目录下的.jupyter文件夹里生成jupyter_notebook_config.py。用编辑器打开,找到对应的行,取消注释并改成你要的值。这里有个版本坑必须提醒:老教程里写的都是c.NotebookApp.port、c.NotebookApp.ip这种写法,但如果你装的是 Notebook 7 或者 Jupyter Server 2.x,配置项已经搬迁到ServerApp下面了:
# 新版写法(Notebook 7 / Jupyter Server 2.x) c.ServerApp.ip = '127.0.0.1' c.ServerApp.port = 8888 c.ServerApp.open_browser = False c.ServerApp.root_dir = '/Users/me/work' c.ServerApp.token = '' # 仅限本机自用,见下方警告同时设NotebookApp和ServerApp两套值也不会报错,但只有生效的那套起作用,改完没反应多半就是改错了对象。
想在同一个局域网里用手机或另一台电脑访问,把ip改成0.0.0.0,然后访问http://本机局域网IP:8888。这时候千万别把 token 设成空字符串——同一网络下的任何设备都能直接进你的文件系统,风险很高。正确做法是设密码:
jupyter notebook password它会提示你输入两遍密码,然后写进jupyter_server_config.json。之后访问时输入这个密码即可。密码本身也是哈希存储的,比明文 token 好管理。
提示:如果你在某个云主机上跑 notebook,更推荐的做法是只监听 127.0.0.1,再用 SSH 端口转发把远端的端口映射到本地浏览器。命令是
ssh -L 8888:127.0.0.1:8888 用户名@主机地址,这样流量全程加密,也不用把端口暴露在公网上。这个方式的配置成本比想象中低,值得花十分钟学一下。
2.4 Notebook 7 和 classic 界面,别被教程带偏
2023 年之后,pip install notebook装到的已经是 Notebook 7,界面基于 JupyterLab 的组件重写过,和经典的 Notebook 6 长得不一样。这个变化带来的实际影响有三条:
- 老一代的 nbextensions 扩展(就是那个经典的 Nbextensions 配置页)基本失效,装上去也看不到标签页。
- 快捷键有变化,命令模式下的
Esc进入、A上方插入、B下方插入、DD删除这些还好,但部分插件快捷键没了。 - 想找回经典界面的人可以装
pip install notebook==6.5.7,但这条路的长期维护性堪忧,新项目不建议回退。
我个人现在的组合是:JupyterLab 当主力(多文件、多面板、终端一体),Notebook 用来做单文件演示和交付。两个可以装在同一个环境里,不冲突。
3. 执行模型拆解:单元格、内核和那个"骗人"的 In[ ]
3.1 前端与内核分离,顺序幻觉从哪来
这是理解一切"诡异现象"的钥匙。你看到的浏览器页面是前端,跑代码的是内核进程。前端把 cell 里的代码通过 ZeroMQ 发过去,内核执行完把输出发回来,前端渲染。这中间有三件事值得注意:
第一,In [ ]里的数字只表示"第几次被提交执行",不表示依赖顺序。你先把第 3 个 cell 跑了,再改第 1 个 cell 里的变量然后跑第 1 个,最后从头Run All——中间那些结果可能是用旧变量算出来的。这就是所谓的 notebook 顺序幻觉,它最恶心的地方在于你不重启内核就永远发现不了。
第二,输出是缓存下来的快照。你看到的那张图、那个 DataFrame,是当时那一刻的结果。变量后来变了,图片不会自己更新。
第三,内核是可以"假死"的。前端还在、cell 还能编辑,但内核进程已经崩了或者卡在某个死循环里,这时候你敲什么都是[*]没反应。
对付这三条的办法,说来简单但必须养成习惯:
- 每次要分享或提交结果之前,执行Kernel → Restart Kernel and Run All Cells。跑得通,说明这个 notebook 是自洽的;跑不通,说明你依赖了某个"历史遗留变量"。
- 变量命名尽量不重复使用。同一个
df前半段是原始数据、后半段是清洗结果,这种写法早晚出事。 - 大计算量的结果,用完早点
del掉并且%reset一下心里有数。
3.2 单元格类型与魔法命令,真正的高频工具
一个 cell 有三种类型:Code、Markdown、Raw。Markdown cell 支持标准 Markdown 加一部分 LaTeX,写公式用$...$和$$...$$。大多数新手只用 Code cell,把说明文字全都写成注释,这是巨大的浪费——notebook 的价值有一半在 Markdown 里。一份好的 notebook,读起来应该像一篇带可执行代码的报告,而不是一堆代码加几行注释。
魔法命令里我实际高频使用的就这么几个:
%timeit sorted(range(1000), key=lambda x: -x) # 微基准测试,自动决定重复次数 %%time # 整个 cell 计时,看数据加载耗时够用 %matplotlib inline # 新版本默认就是 inline,老环境里还得手动加 %config InlineBackend.figure_format = 'retina' # 高分辨率屏幕下图更清楚 %load_ext autoreload %autoreload 2 # 改动外部 .py 文件后自动重载,开发模块时救命 %who / %whos # 查看当前命名空间里有啥变量,排查顺序问题很好用 %reset -f # 清空所有变量,比重启内核快%whos这个命令我要额外说一句。当你怀疑"这个变量到底是不是我最新算的",%whos会列出变量名、类型和值。配合%who_ls还能拿到变量名列表做批量清理,比一个个del高效。
另外提一个新手常问的问题:Python 内置函数在 notebook 里怎么用?和普通脚本完全一样。比如abs(-3.5)取绝对值,int('42')、float('3.14')、str(100)做类型转换,list 去重筛选可以用list(dict.fromkeys(...))或者set(),这些在 cell 里随手就能试,即时看到结果是 notebook 最舒服的地方。
3.3 内存、缓存与变量的真实生命周期
内核重启,所有变量、导入的模块、定义过的函数,全部消失。内核不重启,它们就一直在内存里躺着。这两句话听起来像废话,但它导致两个很实际的问题。
一个是内存泄漏感。你反复加载了几个大 DataFrame、画了几十张图,内存越吃越多,最后内核被系统杀掉。判断方法是%whos看变量体积,或者用psutil打印当前进程内存:
import os, psutil p = psutil.Process(os.getpid()) print(f"{p.memory_info().rss / 1024 / 1024:.1f} MB")另一个是 matplotlib 的图形对象堆积。画图时如果用plt.plot()而不关闭,图会挂在全局状态上,画到几百张之后渲染变慢。养成习惯:每个 cell 里画完图加一句plt.show(),需要多张独立图时用fig, ax = plt.subplots()显式创建,画完plt.close(fig)。
还有个大坑是在 notebook 里定义了一个函数,然后改了外部.py文件里的实现,却忘了重启内核,结果跑出来还是旧逻辑。这就是%autoreload 2存在的意义,加上这两行,模块级改动基本能自动生效(注意:已经被导入的具体对象引用不会变,还是要重启)。
4. 提效三件套:自动补全、目录、外部编辑器
4.1 代码自动补全的几种实现路径
jupyter notebook代码自动补齐这个词的搜索量说明默认体验确实不够好。分情况说:
Notebook 7 和 JupyterLab 4 自带补全。敲import pa之后按Tab,或者等它自己弹,就有候选列表了。这是在ipykernel里集成jedi实现的,开箱可用。如果你的版本比较老(Notebook 6),默认只有Tab触发的有限补全,想让它像 IDE 一样实时弹,就得靠扩展。
老版本装 nbextensions。注意只对 Notebook 6 有效:
pip install jupyter_contrib_nbextensions jupyter contrib nbextension install --user pip install jupyter_nbextensions_configurator jupyter nbextensions_configurator enable --user重启后首页会多一个 Nbextensions 标签,里面把 Hinterland(自动补全提示)、Table of Contents (2)、Codefolding 这几个勾上。如果你装的是 Notebook 7,这套东西不生效,别浪费时间。
JupyterLab 装 LSP 补全。想要"跳转到定义""查看函数签名""重构"这些 IDE 级别的功能,装语言服务器:
pip install jupyterlab-lsp python-lsp-server[all]装完重启,右键编辑器里能看到 LSP 相关菜单。这个方案在 JupyterLab 上体验接近 VS Code,代价是首次加载稍慢,大项目里内存占用会上来一点。
VS Code 里写 notebook 是另一条路,而且体验相当好:装 Python 扩展和 Jupyter 扩展,.ipynb文件直接打开,补全是 Pylance 提供的,比 jedi 强不少,还能顺便用# %%把.py文件切成单元格跑。唯一的坑是内核选择——状态栏右上角一定要选对虚拟环境,选错就出现"模块找不到"的假故障。
我自己现在的分工是:探索阶段用 VS Code 或 JupyterLab(补全好、快捷键熟),要出图和写讲解文字的时候切回 Notebook 界面(渲染和排版更贴近最终交付形态)。
4.2 Markdown 目录与锚点跳转
jupyter notebook怎么生成markdown目录语法这个问题,答案分两层。
第一层,手动锚点。Markdown cell 里写:
[跳转到数据清洗](#数据清洗) ...(中间隔很多内容)... ## 数据清洗Jupyter 会把标题渲染成 HTML,标题文字会生成一个 id,规则大致是:转小写、空格换成连字符、去掉大部分标点。中文标题一般能直接对应。所以#数据清洗能跳,但标题里带英文和数字混排的时候锚点规则容易猜错,最可靠的办法是点一下标题链接看浏览器地址栏,把#后面那段原样复制过来。
第二层,自动生成目录。分工具说:
- JupyterLab 内置左侧 TOC 面板(View → Table of Contents),自动扫描全文标题,点一下就跳,这是最省事的方案。
- 想在文档里插入一个可点击的目录块,装
jupyterlab-toc之类扩展,或者在第一个 cell 里手写一个 Markdown 列表,配上手动锚点。 - 用 nbconvert 导出 HTML 时,可以加参数自动生成带目录的页面。
顺带说个 Markdown 排版的实操心得:标题层级只用#到###。Jupyter 的锚点生成对####及更深的层级支持不稳定,而且一个 notebook 里出现五级标题,阅读体验本身就不好。内容太细就拆到新 cell 或者拆到新 notebook。
如果你还想在正文里插入本地图片、插入数学公式、插入代码块里的语法高亮,Markdown cell 都支持。代码块用三个反引号加语言名,语法高亮就会生效。这些小细节堆起来,notebook 的观感完全不一样。
4.3 和 nvim、VS Code 接力干活
jupyter notebook nvim这类组合需求,说明有人不满足于在浏览器里写代码。可行的路子有这几条:
路子一,Jupytext 双向同步。装pip install jupytext,然后把.ipynb和.py配对。配置好之后,你在 nvim 或 VS Code 里编辑.py,保存时 notebook 自动同步;反过来也一样。
jupytext --set-formats ipynb,py:percent myfile.ipynb jupytext --sync myfile.ipynbpy:percent格式用# %%分隔单元格,vim 里配合 vim-slime 或者 molten 插件,可以做到"选中一段代码,扔给正在运行的 Jupyter 内核执行,结果在旁边的窗口显示"。这套流程在远程服务器上写代码时特别舒服。
路子二,让 nvim 直接接入运行中的内核。jupyter console支持--existing参数连到一个已经跑起来的内核:
jupyter console --existing kernel-abc123.json终端里就能用In [1]:的交互提示符操作同一个命名空间。缺点是输出渲染比较朴素,画图看不到。
路子三,VS Code 原生。这个是成本最低的方案。VS Code 打开.ipynb、装好 Jupyter 扩展、选好内核,剩下的和浏览器里几乎一样,还能同时用 Git 面板、终端、调试器。vscode python环境配置这个问题的核心其实就三件事:选对解释器(Ctrl+Shift+P → Python: Select Interpreter)、确认python.pythonPath或新版的python.defaultInterpreterPath指向虚拟环境、把虚拟环境的Scripts(或bin)目录加进终端 PATH。三件事都对,90% 的"模块找不到"问题消失。
路子四,nvim 里直接开。有jupyter-vim-binding这类插件,把浏览器的快捷键映射到 vim,不过它的维护状态一般,Notebook 7 下基本没戏。真要在终端里写 notebook,我更推荐jupytext + vim-slime或者干脆 JupyterLab。
注意:跨编辑器同步
.ipynb的时候,最容易出问题的就是同一个文件两边同时改。Jupytext 的同步是文件级的,冲突了它会报错甚至覆盖。我的做法是:同一时间只在一个编辑器里改,改完手动jupytext --sync一次,别开着自动同步又开着两个窗口。
5. 高频故障排查实录
5.1 打不开、起不来,从终端报错看起
jupyter notebook打不开是搜索热度最高的一类问题。排查顺序我总结成一条链:先看终端有没有报错 → 再看端口 → 再看浏览器 → 最后看内核。
终端没报错,浏览器就是不弹。这通常是被--no-browser配置项挡住了,或者系统默认浏览器关联坏了。终端里会打印一个形如http://localhost:8888/tree?token=...的地址,直接复制到浏览器打开。注意那个 token 必须带全,漏了会被要求输入密码。
终端报端口占用。报错长这样:OSError: [Errno 98] Address already in use或者 Windows 上的[Errno 10048]。先看看是谁占着:
# Linux / macOS lsof -i:8888 # Windows netstat -ano | findstr :8888要么杀掉那个进程,要么换个端口jupyter notebook --port 8899。最省事的其实是找到残留的 Jupyter 进程:你自己开了两个终端都起了服务,第一个没关,第二个自然抢不到端口。
jupyter命令找不到。Windows 上极常见,原因是jupyter.exe装在Python安装目录\Scripts里,而这个目录没进 PATH。两种解决办法:一是把 Scripts 目录手动加进系统环境变量并重开终端;二是绕过命令行,直接python -m notebook,这个写法不依赖 PATH,我一直推荐新手用。
首页打得开,点开某个 notebook 报错。多半是文件本身的问题:JSON 被写坏了、文件太大(几百 MB 的 notebook 前端渲染会卡死)、路径里有特殊字符。先jupyter nbconvert --to notebook --nbformat 4 问题文件.ipynb试着转一遍,能转就说明结构还好;转不了就备份后用文本编辑器翻文件尾部。
改了配置文件不生效。检查三件事:改的是不是当前生效的那份配置(jupyter --paths能看到所有配置目录)、配置项前缀是不是写成了旧版的NotebookApp、有没有重启服务。配置文件是启动时读一次的,改完必须重启。
5.2 单元格执行没反应,一直在 [*]
jupyter notebook单元格执行代码没有任何反应,这个现象背后至少有六种不同原因,必须分开排查。
原因一,内核根本没连上。看右上角的圆圈状态:空心圆表示空闲,实心表示忙,如果显示"连接失败"或者一个感叹号,那前端跟内核已经断了。处理方式:Kernel 菜单 → Restart Kernel,或者直接关掉服务重开。
原因二,上一个 cell 还在跑。Jupyter 的默认执行模型是串行的,同一个内核里同时只能跑一个 cell。前面有个while True或者一个耗时十分钟的训练循环,后面所有 cell 提交都排队,全都显示[*]。解决办法:Interrupt Kernel(菜单或按两次I),别直接关浏览器——关浏览器杀不掉内核进程。
原因三,代码里有input()或者需要交互的东西。老版本 notebook 对input()支持很差,会一直卡着等输入,而输入框根本不显示。新版本好一些但也不可靠。排查办法是回看终端窗口——内核的标准输出和错误默认会同步打印在启动服务的那个终端里,那里能看到真实的堆栈和提示。
原因四,无限循环或者超大计算。这个不用多说,看 CPU 占用就知道了。写循环调试的时候,习惯性加个计数器或者用tqdm显示进度,出问题一眼能看出来。
原因五,输出量爆炸。比如在循环里print几十万行,前端渲染直接卡死,表现就是整个页面无响应,连菜单都点不动。预防办法:调试循环时不要在里面 print,用tqdm或者每 1000 次打印一次。已经卡死了就重启服务,用编辑器删掉那个 cell 再打开。
原因六,内核崩了。常见于内存爆掉或者底层库的段错误。终端里会看到Kernel died之类的字样。重启内核,然后把代码拆小、分批处理,别一次加载几百兆的数据。
提示:排查"没反应"最有效的动作是把启动服务的终端窗口留在视野里。Jupyter 的很多错误只在终端显示,浏览器里一片安静。我调试内核问题的习惯是两个屏幕,一边浏览器一边终端。
5.3 ImportError: DLL load failed while importing rpds
这个报错近两年问的人特别多,值得单独拆开讲。先说它从哪来:rpds(rpds-py包)是pyrsistent的底层实现之一,用 Rust 写的,被referencing引用,而referencing是jsonschema的依赖,jsonschema又是jupyter_events、nbformat等一堆 Jupyter 组件的依赖。所以这条链是:
jupyter_server → jupyter_events → jsonschema → referencing → rpds-py
也就是说,这个 DLL 报错跟 Jupyter 本身没直接关系,是依赖链底层的一个二进制包在你这台机器上加载失败了。加载失败的原因主要有这几种:
第一,Python 版本和 wheel 不匹配。rpds-py是预编译好的二进制轮子,如果你的 Python 版本太新或者太老,pip 找不到对应的 wheel 就会去下源码包自己编译。Windows 上编译需要 Rust 工具链,绝大多数人没有,于是要么编译失败,要么编出来一个和当前解释器 ABI 不兼容的东西。诊断命令:
python -c "import sys, platform; print(sys.version); print(platform.architecture())" pip show rpds-py看pip show输出的版本号,再去 PyPI 上确认这个版本有没有你当前 Python 版本的 wheel。
第二,32 位和 64 位的错配。装了一个 32 位的 Python,却拉到了 64 位的包(这种情况少见但不为零);或者系统里有多套 Python,python命令指向的和 Jupyter 内核用的不是同一个。
第三,缺少系统运行库。Windows 上需要对应版本的 Microsoft Visual C++ 运行库。装过 Visual Studio 或者很多游戏的人一般都有,纯净系统上可能缺。这类问题不止影响rpds,也会让pyzmq、numpy、cv2等包报类似的 DLL 错误。
第四,pip 和 conda 混装导致的包版本打架。同一个环境里,conda 装的jsonschema配 pip 装的rpds-py,版本对不上就可能加载失败。
处置顺序我建议这样:
# 1. 先看看谁依赖它、版本是否对得上 pip check # 2. 强制重装,别用缓存 pip install --force-reinstall --no-cache-dir rpds-py # 3. 还是不行的,退到有匹配 wheel 的版本区间(具体版本看 PyPI 页面对应 Python 版本的标签) pip install "rpds-py<0.20" # 4. conda 环境里优先用 conda 装 conda install -c conda-forge rpds-py实测最有效的三板斧:一是确认 Python 版本别太激进(3.11 这种主流版本上 wheel 覆盖最全);二是--force-reinstall --no-cache-dir清掉可能损坏的旧文件;三是把整个环境重建一遍——虚拟环境删掉重装,比在坏环境里修半天快。
注意:如果你在 CMD 里装了包,但 Jupyter 内核用的是另一个 Python,那怎么修都没用。先跑
import sys; print(sys.executable)在 notebook 里确认解释器路径,再看这个路径下有没有那个包。这一步能排除掉一大半"假故障"。
同类问题还有ImportError: DLL load failed while importing _ssl(缺 OpenSSL 相关库)、while importing qt(PyQt 环境错乱)、cv2装不上或导入失败(多半是 OpenCV 的轮子和 numpy 版本或 Python 版本不匹配,建议指定版本区间安装)。排查思路是统一的:确认解释器路径 → 确认包版本和 Python 版本匹配 → 确认架构一致 → 强制重装或重建环境。
5.4 内核找不回、画图不出结果和其它杂症
内核列表里出现"Python 3 (ipykernel)"但点了报错。这个内核指向的解释器路径已经不存在了(环境被删、目录被移动)。jupyter kernelspec list找到路径,jupyter kernelspec remove 名字删掉,再重新注册。
notebook 里import pandas成功,import 自己写的模块失败。根因是当前工作目录不在sys.path里了。老版本 notebook 默认会把 notebook 所在目录加进sys.path,新版本或某些配置下不一定。稳妥做法是在 notebook 开头加:
import sys, os sys.path.insert(0, os.path.abspath('..'))或者把项目做成可安装的包pip install -e .,一劳永逸。
matplotlib 画图不出图,只显示<Figure size ...>。加%matplotlib inline;如果用了fig, ax的写法,确认最后调用了plt.show()或者把fig单独放在 cell 最后一行。
横坐标标签太密集糊成一团。这是python画图横坐标太密集的经典问题,几个办法按效果排序:
import matplotlib.pyplot as plt from matplotlib.ticker import MaxNLocator fig, ax = plt.subplots(figsize=(12, 4)) ax.plot(x, y) # 方案一:标签旋转 ax.tick_params(axis='x', rotation=45) # 方案二:限制刻度数量 ax.xaxis.set_major_locator(MaxNLocator(nbins=10)) # 方案三:日期轴自动格式化 fig.autofmt_xdate() # 方案四:加大画布尺寸,减少纵向挤压 plt.tight_layout() plt.show()实际组合使用效果最好:先figsize给足宽度,再限制刻度数量,最后旋转标签。顺序反了容易白调。
图里中文显示成方块。指定中文字体:
plt.rcParams['font.sans-serif'] = ['SimHei'] # Windows # plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # macOS plt.rcParams['axes.unicode_minus'] = Falsenotebook 里的输出越堆越多、滚动卡顿。用 Cell → All Output → Clear 清一遍,或者存文件前用nbstripout把输出全部剥掉。
5.5 常见问题速查表
| 现象 | 最可能的原因 | 第一步动作 |
|---|---|---|
命令找不到jupyter | Scripts 目录不在 PATH | 改用python -m notebook |
| 提示端口被占用 | 有残留服务进程 | 换端口或lsof -i:8888排查 |
| 浏览器不弹出 | 配置了--no-browser | 复制终端里的地址手动打开 |
单元格一直[*] | 前一个 cell 没跑完 / 内核卡死 | Interrupt Kernel,看终端输出 |
| 整个页面无响应 | 输出量太大 | 重启服务,删掉爆输出的 cell |
ModuleNotFoundError | 内核选错了解释器 | 在 cell 里打印sys.executable |
| DLL load failed | 二进制包版本或架构不匹配 | pip check+ 强制重装 |
| 内核重启后变量消失 | 正常行为,不是 bug | 用%store或存文件持久化 |
| 导入本地模块失败 | 工作目录不在sys.path | sys.path.insert(0, ...) |
| 中文乱码 | 没指定中文字体 | 设置font.sans-serif |
6. 从草稿纸到可交付:notebook 的工程化收尾
6.1 目录组织与依赖冻结
notebook 天生容易长成一堆散落在下载目录里的Untitled1.ipynb。我现在的目录结构大概是这样:
project/ ├── data/ # 原始数据,只读 ├── notebooks/ # 探索用 notebook,编号排列 │ ├── 01-explore.ipynb │ └── 02-model.ipynb ├── src/ # 稳定下来的函数抽取到这里 │ └── feature.py ├── outputs/ # 导出的图表和报告 ├── requirements.txt └── README.md两个细节决定这套结构能不能坚持下来。第一,notebook 一律用编号前缀,01-、02-,文件管理器里自然按时间顺序排,比按名字排强。第二,src/里的模块要用%autoreload加载,改动即时生效,这样你才有动力把重复代码抽出去,而不是一直复制粘贴。
依赖冻结这一步千万别省:
pip freeze > requirements.txt换机器、隔了三个月回来跑,pip install -r requirements.txt就能复现环境。但要注意pip freeze会把你环境里所有包都写进去,包括临时装来试的。干净的做法是在虚拟环境里只装项目必需的包,或者在文件里手动维护一个精简列表。conda 环境用conda env export --no-builds > environment.yml。
6.2 自动执行与报告导出
notebook 真正发挥威力是在"一键跑完并出报告"这件事上。nbconvert是内置的工具:
# 导出 HTML,并且先重新执行一遍(确保结果是最新的) jupyter nbconvert --to html --execute notebooks/02-model.ipynb --output-dir outputs/ # 导出 Markdown,方便贴进文档系统 jupyter nbconvert --to markdown notebooks/02-model.ipynb # 导出成 Python 脚本,做代码审查用 jupyter nbconvert --to script notebooks/02-model.ipynb--execute这个参数是关键。它保证导出的是真实执行过的最新结果,而不是文件里陈旧的缓存输出。做定期报表的场景里,把这个命令挂到系统的定时任务上,notebook 就变成了一个会自动更新的报告生成器。
需要传参数跑不同数据集的时候,用papermill:
pip install papermill papermill notebooks/02-model.ipynb outputs/run-2024.ipynb -p date 2024-01-01在 notebook 里用一个标记了parameters的 cell 声明参数名,papermill 会在执行时注入。这一套在批量跑实验的时候非常好用。
最后是版本控制的老问题。.ipynb的 JSON 里塞满了输出和执行计数,两个人改同一个小地方,Git diff 能出来几千行。解决办法是装nbstripout,提交前自动剥掉输出:
pip install nbstripout nbstripout --install # 在当前仓库装 git filter剥掉输出的 notebook 只留代码和 Markdown,diff 清爽得多。代价是别人 clone 下来看到的是没有结果的版本,得自己跑一遍。团队里如果很在意结果快照,那就保留输出,改用大文件存储或者干脆导出 HTML 一起提交。
6.3 我个人坚持的几个习惯
写到这里,讲几个我用了几年之后固化下来的习惯,都是被坑出来的。
第一个,每个 notebook 的第一行永远是 Markdown 标题加一句话说明。写上这是干什么的、数据从哪来、跑一遍大概多久。三个月后的你会感谢现在的你。
第二个,不在 notebook 里写超过 30 行的函数。抽到.py里去,用%autoreload加载。notebook 里的长函数没法单元测试,改一次跑一次,效率极低。
第三个,交付前一定 Restart & Run All。我见过太多次"本机跑得好好的、别人打开全是NameError"的场面,根因都是依赖了历史变量。这一个动作能挡掉 80% 的交付事故。
第四个,大计算的结果及时落盘。训练了二十分钟的模型,跑完立刻joblib.dump或者to_parquet存下来,别让它只活在内核内存里。内核一崩,二十分钟白费。我踩过这个坑,至今记得那个下午。
第五个,调试循环不要在循环体内 print。用tqdm显示进度,或者每千次打一行。这条规则帮我省下的时间,比任何优化技巧都多。
第六个,别信"我待会儿再整理"。notebook 一旦变成垃圾场就很难收拾。每做完一个阶段就顺手清掉失败的实验 cell、删掉没用的变量、把结论写成 Markdown。一个能给人看的 notebook,价值比十个自己都看不懂的草稿高得多。
最后分享一个小技巧收尾:如果你的 notebook 里有一段代码要反复跑、但每次只改一两个参数,把它写成函数,然后上面加一个 cell 用@interact装饰器配ipywidgets,就能得到一个带滑块和输入框的小控制面板:
from ipywidgets import interact @interact(n=(1, 50), scale=(0.1, 2.0, 0.1)) def demo(n=10, scale=1.0): print([round(i * scale, 2) for i in range(n)])拖一下滑块结果就刷新,调参效率比来回改代码高一个数量级。这个小东西我第一次用的时候,感觉像给 notebook 装上了仪表盘。