拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Jupyter Notebook 从入门到工程化:安装、内核与故障排查

Jupyter Notebook 从入门到工程化:安装、内核与故障排查

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.ipynb

py: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'] = False

notebook 里的输出越堆越多、滚动卡顿。用 Cell → All Output → Clear 清一遍,或者存文件前用nbstripout把输出全部剥掉。

5.5 常见问题速查表

现象最可能的原因第一步动作
命令找不到jupyterScripts 目录不在 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.pathsys.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 装上了仪表盘。

返回列表