1. 从一个让人抓狂的报错说起如果你在 VS Code 里用 Jupyter 跑过代码,大概率见过这个弹窗:"Kernel died with exit code 1"。它不像语法错误那样给你一行红色波浪线,也不像缺包那样告诉你ModuleNotFoundError,它就是一个冷冰冰的退出码,然后内核直接没了。你点重启,它又死;你换一个 notebook,它还是死;你甚至开始怀疑是不是电脑该换了。我前后在三个不同的项目里踩过这个坑,每次的根因都不一样。最坑的一次是:同一个 notebook,在浏览器里打开 Jupyter Lab 跑得好好的,一放到 VS Code 里就必死。这就说明问题不在代码本身,而在环境链路上。VS Code 的 Jupyter 扩展并不是自己实现了一个内核,它本质上是去调用你系统里已经装好的 Python 解释器和ipykernel,中间隔了好几层:VS Code 扩展 → Python 扩展 → 选中的解释器 → 该解释器下的 ipykernel → 实际启动的 kernel 进程。任何一层对不上,都可能以 exit code 1 收场。这篇文章就是把这个链路拆开,一层一层告诉你哪里容易断,以及断了之后怎么快速定位。适合两类人:一类是刚配好多环境、正准备大干一场结果被这个报错劝退的;另一类是已经能跑,但想搞清楚"为什么换个环境就崩"的。我会把每个环节的排查命令、判断依据、以及我实际踩过的坑都写清楚,你照着做基本能覆盖九成以上的场景。2. 先搞清楚 exit code 1 到底是谁报的2.1 内核进程的启动链路很多人一看到 "Kernel died" 就以为是 Jupyter 的问题,其实这个报错是 VS Code 的 Jupyter 扩展在子进程退出时抛出来的。它启动 kernel 的方式大致是这样:扩展先通过 Python 扩展拿到你当前选中的解释器路径,然后执行类似python -m ipykernel_launcher -f {connection_file}的命令,把内核作为一个独立进程拉起来。这个进程如果启动过程中抛了未捕获异常、或者直接sys.exit(1),扩展就会收到退出码 1,然后弹出那句话。所以 exit code 1 的含义是:内核进程自己退出了,而且是非正常退出。它可能是 Python 解释器根本没起来,可能是ipykernel没装,可能是ipykernel_launcher导入时报错,也可能是内核启动后连不上 ZMQ 端口。范围很宽,但好消息是——真正的错误信息通常被吞掉了,我们需要主动把它捞出来。2.2 为什么错误信息经常看不到VS Code 默认只显示一句 "Kernel died",因为内核进程的 stderr 被扩展接管了,普通输出面板里看不到。这时候有两个地方能救命:Output 面板:Ctrl+Shift+U打开 Output,右上角下拉选 "Jupyter",这里会打印扩展启动内核的完整命令和部分 stderr。手动复现:把 Output 里那条启动命令复制出来,在终端里手动跑一遍,错误就会原原本本打在你脸上。我强烈建议养成"手动复现"的习惯。因为 VS Code 的日志有时候会截断,而终端不会骗你。下面这条命令就是典型的内核启动命令形态:/path/to/python -m ipykernel_launcher -f /tmp/kernel-xxxx.json如果你手动跑这条命令,看到的是ModuleNotFoundError: No module named 'ipykernel',那问题就非常明确了——当前解释器没装 ipykernel。如果看到的是别的,继续往下看。2.3 一个容易被忽略的前提:解释器选中状态在动手排查之前,先确认一件事:VS Code 当前选中的解释器,和你以为的那个是不是同一个。这个听起来很蠢,但我见过太多次了。VS Code 右下角、命令面板Python: Select Interpreter、以及 notebook 右上角的 kernel 选择器,这三处显示的可能是三个不同的东西。尤其是当你用 conda 或 venv 建了多个环境时,VS Code 会记住"上次选中的解释器",而 notebook 又会记住"上次用的 kernel"。两者一旦错位,就会出现"我在 A 环境装了包,结果它用 B 环境跑"的诡异现象。排查第一步永远是:打开一个终端,确认which python和python -c "import sys; print(sys.executable)"的输出,和 VS Code 里选中的解释器路径完全一致。不一致就先统一,再谈别的。3. 环境错位:最常见的头号嫌疑3.1 多环境下的解释器与 kernel 错配这是我自己踩得最多的一类。场景很典型:我用 conda 建了个环境叫proj-a,在里面装了 pandas、numpy、ipykernel,然后打开 VS Code,右下角选了解释器proj-a,notebook 右上角也选了proj-a,结果一跑就死。手动复现发现,它实际调用的是 base 环境的 python。原因在于:VS Code 的 kernel 选择器里显示的 kernel 列表,来自jupyter kernelspec list,而不是来自你当前选中的解释器。也就是说,即使你选了解释器proj-a,如果proj-a没有注册成 kernelspec,VS Code 可能会 fallback 到某个已注册的 kernel(比如 base 的),或者干脆用一个默认路径去启动,导致环境错位。判断方法很简单,在终端里跑:jupyter kernelspec list看看列出来的 kernel 路径,是不是你期望的那个环境。如果proj-a根本不在列表里,那 VS Code 里选的那个"proj-a kernel"很可能是假的,或者指向了别处。3.2 手动注册 kernelspec 的正确姿势解决办法是给目标环境显式注册一个 kernelspec。注意,不要用--user和--sys-prefix混着来,这是另一个坑。推荐的做法是:先激活目标环境,然后在该环境内注册。conda activate proj-a python -m ipykernel install --user --name proj-a --display-name "Python (proj-a)"这里--user会把 kernelspec 写到用户目录(Linux/macOS 是~/.local/share/jupyter/kernels/,Windows 是%APPDATA%\jupyter\kernels\),好处是不污染系统环境,多个环境互不干扰。--name是内部标识,--display-name是 VS Code 里显示的名字,建议把环境名写进去,方便区分。注册完之后,重启 VS Code(不是重载窗口,是完全退出再打开),因为 kernel 列表是在启动时缓存的。重启后在 notebook 右上角重新选一次,这次应该能看到你刚注册的那个。注意:如果你用的是 venv 而不是 conda,注册命令一样,但激活方式换成source venv/bin/activate(Linux/macOS)或venv\Scripts\activate(Windows)。venv 环境默认不会自动注册 kernelspec,必须手动来一次。3.3 一个隐蔽的坑:kernelspec 里的 argv 写死了路径注册完 kernelspec 后,它会在对应目录下生成一个kernel.json。这个文件里有个argv字段,写的是启动内核的完整命令。如果你后来移动了环境目录,或者重命名了 conda 环境,这个argv里的路径就失效了,内核启动时找不到 python,直接 exit code 1。我遇到过一次:把 conda 环境从D:\envs\proj-a挪到了E:\envs\proj-a,结果所有 notebook 全崩。排查了半天才想起来是kernel.json里的路径没更新。解决办法就是删掉旧的 kernelspec 重新注册:jupyter kernelspec remove proj-a # 然后重新 install或者直接手动编辑kernel.json里的路径。但手动编辑容易漏,还是重新注册稳妥。4. ipykernel 缺失或版本不匹配4.1 新环境里最容易漏装的东西新建一个 conda 环境后,很多人第一件事是pip install pandas numpy,然后打开 VS Code 写 notebook,结果内核直接死。原因就是新环境里没有 ipykernel。ipykernel不是 Python 自带的,也不是 conda 建环境时默认装的(除非你用了anaconda这个 meta 包),必须手动装。conda activate proj-a pip install ipykernel # 或者 conda install ipykernel装完之后再注册 kernelspec,顺序不能反。我见过有人先注册再装 ipykernel,结果注册时用的是旧状态,kernel.json 里指向的模块不存在,照样死。4.2 版本冲突:ipykernel 和 jupyter_client 打架比"没装"更隐蔽的是"装了但版本不兼容"。ipykernel依赖jupyter_client,而jupyter_client又依赖pyzmq。这三个包的版本如果对不上,内核启动时会在建立 ZMQ 连接那一步挂掉,报错信息往往是一堆 traceback,最后以 exit code 1 结束。典型症状是:手动跑python -m ipykernel_launcher -f xxx.json时,看到类似ImportError: cannot import name 'xxx' from 'jupyter_client'或者zmq.error.ZMQError。这时候不要一个个去猜版本,直接用 pip 的依赖解析来修:pip install --upgrade ipykernel jupyter_client pyzmq如果升级后还是不行,可能是某个包被 conda 和 pip 混装搞乱了。这种情况我建议在一个干净环境里重装,而不是在污染环境里修。因为 conda 和 pip 混用导致的依赖树错乱,修起来的时间成本远高于重建。4.3 用 conda 还是 pip 装 ipykernel这个问题没有绝对答案,但有个原则:如果你这个环境里的其他包主要是 conda 装的,那 ipykernel 也用 conda 装;如果主要是 pip 装的,那就用 pip。混着来最容易出问题,因为 conda 和 pip 各自维护一套依赖记录,互相不知道对方干了什么。我个人的习惯是:科学计算类环境用 conda 装核心包(numpy、scipy、pandas),ipykernel 也跟着 conda;纯 Web 或工具类环境用 pip,ipykernel 也 pip。这样依赖树相对干净,出问题的概率低很多。5. 端口、权限与路径里的暗雷5.1 ZMQ 端口被占用或无法绑定内核和前端之间通过 ZMQ 通信,需要绑定几个本地端口。如果这些端口被占用,或者系统限制导致无法绑定,内核启动就会失败。这种情况在 Windows 上尤其常见,因为 Windows 的端口回收比较慢,频繁重启内核时容易撞上。判断方法:手动启动内核时,如果看到OSError: [Errno 98] Address already in use或者zmq.error.ZMQError: Address already in use,那就是端口问题。解决办法有几个:等一会儿再试,让系统回收端口。重启 VS Code,释放它持有的连接。如果是 Windows,检查是否有残留的 python 进程占着端口,用任务管理器杀掉。# Linux/macOS 查看占用端口的进程 lsof -i :8888 # Windows netstat -ano | findstr :88885.2 工作目录不存在导致的启动失败这个坑非常隐蔽。内核启动时会尝试cd到 notebook 所在的工作目录。如果这个目录不存在(比如你删了文件夹但 VS Code 还开着那个 notebook),或者没有访问权限,内核进程会在初始化阶段直接退出,报 exit code 1。我遇到过一次:把项目文件夹从~/work/proj重命名成了~/work/proj-v2,但 VS Code 的工作区还指向旧路径,结果所有 notebook 全崩。排查时手动跑内核命令,发现它cd到一个不存在的目录,然后就没有然后了。解决办法:关掉 VS Code,重新打开正确的文件夹。如果 notebook 文件本身还在,用"打开文件夹"的方式重新加载,而不是直接双击 notebook 文件。5.3 路径里有中文或空格Windows 用户特别容易踩这个。如果你的 Python 环境路径、或者 notebook 所在路径里包含中文、空格、特殊字符,某些版本的 ipykernel 在拼接命令行参数时会出错,导致内核启动失败。这个问题的表现是:手动跑命令时看起来正常,但 VS Code 启动时就死。规避方法很简单:Python 环境路径和项目路径都用纯英文、无空格。比如D:\envs\proj-a和D:\work\proj-a,不要用D:\我的环境\项目 A。虽然现代工具对中文路径的支持越来越好,但在内核启动这种涉及多层进程调用的场景里,少一个变量就少一个坑。6. 一套可复用的排查流程6.1 从 Output 面板拿到真实命令前面提过,第一步永远是打开 Output 面板选 Jupyter,找到类似这样的日志:Starting kernel: /path/to/python -m ipykernel_launcher -f /tmp/kernel-xxx.json把这条命令完整复制出来。注意,有时候日志里会有多行,要找到最后实际执行的那条。如果日志里没有,可以在 VS Code 设置里把jupyter.logKernelOutput打开,它会打印更多细节。6.2 在终端里手动复现拿到命令后,在终端里跑一遍。这一步的关键是用和 VS Code 相同的 shell 环境。如果你在 VS Code 里用的是 PowerShell,那就在 PowerShell 里跑;如果用的是 bash,就用 bash。因为不同 shell 的环境变量加载逻辑不一样,可能影响 Python 的模块搜索路径。手动跑的时候,观察输出:如果报No module named 'ipykernel',去装 ipykernel。如果报ImportError或AttributeError,是版本冲突,去升级相关包。如果报Address already in use,是端口问题,去释放端口。如果报FileNotFoundError或PermissionError,是路径或权限问题。如果什么都没报,进程直接退出,那可能是kernel.json配置有问题,去检查 kernelspec。6.3 逐层验证的检查清单为了让你少走弯路,我把整个链路的检查点整理成一张表,按顺序排查即可:检查层检查内容命令/方法常见问题解释器VS Code 选中的解释器路径Python: Select Interpreter选错环境解释器终端里 python 的实际路径which python/where python与 VS Code 不一致包ipykernel 是否安装pip show ipykernel新环境漏装包依赖版本是否兼容pip checkjupyter_client 冲突kernelspec是否注册jupyter kernelspec list未注册或路径失效kernelspeckernel.json 里的 argv查看对应目录下文件路径写死且已失效端口ZMQ 端口是否可用lsof -i/netstat -ano端口被占用路径工作目录是否存在ls/dir目录被删或改名路径是否含中文/空格目测特殊字符导致解析失败按这个表从上往下走,基本能在十分钟内定位到问题层。我自己的经验是,前四行能解决八成问题,后面几行属于偶发但一旦遇到就很折磨的类型。6.4 重建环境:最后的杀手锏如果排查了一圈还是没头绪,或者环境已经被 conda/pip 混装搞得千疮百孔,那就别修了,直接重建。重建的步骤:conda create -n proj-new python=3.11 conda activate proj-new pip install ipykernel pandas numpy matplotlib python -m ipykernel install --user --name proj-new --display-name "Python (proj-new)"然后重启 VS Code,选新环境。整个过程十分钟以内,比在一个坏环境里折腾两小时划算得多。我现在的原则是:环境一旦出现无法解释的内核崩溃,且排查超过二十分钟,直接重建。时间比什么都贵。7. 几个我实际踩过的真实场景7.1 场景一:conda 环境重命名后全崩有一次为了统一命名规范,我把几个 conda 环境从env1、env2改成了proj-data、proj-model。改完之后,所有 notebook 全部报 kernel died。手动跑内核命令,发现kernel.json里的argv还指向旧路径env1/bin/python,而这个路径已经不存在了。解决方式就是删掉旧 kernelspec 重新注册。但这里有个细节:jupyter kernelspec remove用的是--name参数,也就是注册时的内部标识,不是显示名。如果你忘了内部标识是什么,可以去 kernelspec 目录下看文件夹名,那个就是。7.2 场景二:Windows 下路径含空格导致启动失败某次在 Windows 上,Python 装在C:\Program Files\Python311,路径里有空格。VS Code 启动内核时,命令行参数拼接没有正确加引号,导致ipykernel_launcher收到的-f参数被截断,内核启动失败。手动在终端里跑同样的命令却正常,因为终端会自动处理引号。这个问题的根因在扩展的参数拼接逻辑上,用户层面能做的就是把 Python 装到无空格路径,比如C:\Python311。虽然听起来很原始,但确实能绕开这类问题。7.3 场景三:jupyter_client 版本过新导致不兼容有一次手贱pip install --upgrade jupyter_client,升到了最新版,结果 ipykernel 还是旧版,两者 API 对不上,内核启动时报AttributeError。这种"升级了一个包但没升级配套包"的操作,是版本冲突的经典来源。教训就是:升级 Jupyter 相关包时,要么一起升,要么都别动。如果只想升一个,先看看它的依赖关系,确认配套包是否兼容。pip check可以帮你发现这类问题,但它只在安装后检查,启动时的问题还得靠手动复现来暴露。8. 把坑填上之后的一些习惯踩了这么多次之后,我养成了几个习惯,分享出来供参考。第一,新建环境后第一件事就是装 ipykernel 并注册 kernelspec,不要等到写 notebook 时才想起来。第二,环境路径和项目路径一律用英文无空格,从源头上规避路径解析问题。第三,不轻易混用 conda 和 pip 装同一个环境的包,如果非要混,先装 conda 的,再用 pip 补缺。第四,遇到内核崩溃先看 Output 面板再手动复现,不要盲目重启,重启解决不了根因。还有一个很实用的小技巧:在 notebook 的第一个 cell 里跑import sys; print(sys.executable),确认当前内核用的到底是哪个 Python。这个习惯能帮你在一开始就发现环境错位,而不是等到跑了一半才崩。我现在的每个 notebook 第一个 cell 都是这行,已经成了肌肉记忆。内核崩溃这件事,说到底就是链路太长、信息被吞、环境太乱三个因素叠加。把链路拆开、把信息捞出来、把环境理干净,exit code 1 就不再是玄学,而是一个有明确原因的普通报错。希望这篇记录能帮你少走几个我走过的弯路。
1. 从一个让人抓狂的报错说起如果你在 VS Code 里用 Jupyter 跑过代码,大概率见过这个弹窗:"Kernel died with exit code 1"。它不像语法错误那样给你一行红色波浪线,也不像缺包那样告诉你ModuleNotFoundError,它就是一个冷冰冰的退出码,然后内核直接没了。你点重启,它又死;你换一个 notebook,它还是死;你甚至开始怀疑是不是电脑该换了。我前后在三个不同的项目里踩过这个坑,每次的根因都不一样。最坑的一次是:同一个 notebook,在浏览器里打开 Jupyter Lab 跑得好好的,一放到 VS Code 里就必死。这就说明问题不在代码本身,而在环境链路上。VS Code 的 Jupyter 扩展并不是自己实现了一个内核,它本质上是去调用你系统里已经装好的 Python 解释器和ipykernel,中间隔了好几层:VS Code 扩展 → Python 扩展 → 选中的解释器 → 该解释器下的 ipykernel → 实际启动的 kernel 进程。任何一层对不上,都可能以 exit code 1 收场。这篇文章就是把这个链路拆开,一层一层告诉你哪里容易断,以及断了之后怎么快速定位。适合两类人:一类是刚配好多环境、正准备大干一场结果被这个报错劝退的;另一类是已经能跑,但想搞清楚"为什么换个环境就崩"的。我会把每个环节的排查命令、判断依据、以及我实际踩过的坑都写清楚,你照着做基本能覆盖九成以上的场景。2. 先搞清楚 exit code 1 到底是谁报的2.1 内核进程的启动链路很多人一看到 "Kernel died" 就以为是 Jupyter 的问题,其实这个报错是 VS Code 的 Jupyter 扩展在子进程退出时抛出来的。它启动 kernel 的方式大致是这样:扩展先通过 Python 扩展拿到你当前选中的解释器路径,然后执行类似python -m ipykernel_launcher -f {connection_file}的命令,把内核作为一个独立进程拉起来。这个进程如果启动过程中抛了未捕获异常、或者直接sys.exit(1),扩展就会收到退出码 1,然后弹出那句话。所以 exit code 1 的含义是:内核进程自己退出了,而且是非正常退出。它可能是 Python 解释器根本没起来,可能是ipykernel没装,可能是ipykernel_launcher导入时报错,也可能是内核启动后连不上 ZMQ 端口。范围很宽,但好消息是——真正的错误信息通常被吞掉了,我们需要主动把它捞出来。2.2 为什么错误信息经常看不到VS Code 默认只显示一句 "Kernel died",因为内核进程的 stderr 被扩展接管了,普通输出面板里看不到。这时候有两个地方能救命:Output 面板:Ctrl+Shift+U打开 Output,右上角下拉选 "Jupyter",这里会打印扩展启动内核的完整命令和部分 stderr。手动复现:把 Output 里那条启动命令复制出来,在终端里手动跑一遍,错误就会原原本本打在你脸上。我强烈建议养成"手动复现"的习惯。因为 VS Code 的日志有时候会截断,而终端不会骗你。下面这条命令就是典型的内核启动命令形态:/path/to/python -m ipykernel_launcher -f /tmp/kernel-xxxx.json如果你手动跑这条命令,看到的是ModuleNotFoundError: No module named 'ipykernel',那问题就非常明确了——当前解释器没装 ipykernel。如果看到的是别的,继续往下看。2.3 一个容易被忽略的前提:解释器选中状态在动手排查之前,先确认一件事:VS Code 当前选中的解释器,和你以为的那个是不是同一个。这个听起来很蠢,但我见过太多次了。VS Code 右下角、命令面板Python: Select Interpreter、以及 notebook 右上角的 kernel 选择器,这三处显示的可能是三个不同的东西。尤其是当你用 conda 或 venv 建了多个环境时,VS Code 会记住"上次选中的解释器",而 notebook 又会记住"上次用的 kernel"。两者一旦错位,就会出现"我在 A 环境装了包,结果它用 B 环境跑"的诡异现象。排查第一步永远是:打开一个终端,确认which python和python -c "import sys; print(sys.executable)"的输出,和 VS Code 里选中的解释器路径完全一致。不一致就先统一,再谈别的。3. 环境错位:最常见的头号嫌疑3.1 多环境下的解释器与 kernel 错配这是我自己踩得最多的一类。场景很典型:我用 conda 建了个环境叫proj-a,在里面装了 pandas、numpy、ipykernel,然后打开 VS Code,右下角选了解释器proj-a,notebook 右上角也选了proj-a,结果一跑就死。手动复现发现,它实际调用的是 base 环境的 python。原因在于:VS Code 的 kernel 选择器里显示的 kernel 列表,来自jupyter kernelspec list,而不是来自你当前选中的解释器。也就是说,即使你选了解释器proj-a,如果proj-a没有注册成 kernelspec,VS Code 可能会 fallback 到某个已注册的 kernel(比如 base 的),或者干脆用一个默认路径去启动,导致环境错位。判断方法很简单,在终端里跑:jupyter kernelspec list看看列出来的 kernel 路径,是不是你期望的那个环境。如果proj-a根本不在列表里,那 VS Code 里选的那个"proj-a kernel"很可能是假的,或者指向了别处。3.2 手动注册 kernelspec 的正确姿势解决办法是给目标环境显式注册一个 kernelspec。注意,不要用--user和--sys-prefix混着来,这是另一个坑。推荐的做法是:先激活目标环境,然后在该环境内注册。conda activate proj-a python -m ipykernel install --user --name proj-a --display-name "Python (proj-a)"这里--user会把 kernelspec 写到用户目录(Linux/macOS 是~/.local/share/jupyter/kernels/,Windows 是%APPDATA%\jupyter\kernels\),好处是不污染系统环境,多个环境互不干扰。--name是内部标识,--display-name是 VS Code 里显示的名字,建议把环境名写进去,方便区分。注册完之后,重启 VS Code(不是重载窗口,是完全退出再打开),因为 kernel 列表是在启动时缓存的。重启后在 notebook 右上角重新选一次,这次应该能看到你刚注册的那个。注意:如果你用的是 venv 而不是 conda,注册命令一样,但激活方式换成source venv/bin/activate(Linux/macOS)或venv\Scripts\activate(Windows)。venv 环境默认不会自动注册 kernelspec,必须手动来一次。3.3 一个隐蔽的坑:kernelspec 里的 argv 写死了路径注册完 kernelspec 后,它会在对应目录下生成一个kernel.json。这个文件里有个argv字段,写的是启动内核的完整命令。如果你后来移动了环境目录,或者重命名了 conda 环境,这个argv里的路径就失效了,内核启动时找不到 python,直接 exit code 1。我遇到过一次:把 conda 环境从D:\envs\proj-a挪到了E:\envs\proj-a,结果所有 notebook 全崩。排查了半天才想起来是kernel.json里的路径没更新。解决办法就是删掉旧的 kernelspec 重新注册:jupyter kernelspec remove proj-a # 然后重新 install或者直接手动编辑kernel.json里的路径。但手动编辑容易漏,还是重新注册稳妥。4. ipykernel 缺失或版本不匹配4.1 新环境里最容易漏装的东西新建一个 conda 环境后,很多人第一件事是pip install pandas numpy,然后打开 VS Code 写 notebook,结果内核直接死。原因就是新环境里没有 ipykernel。ipykernel不是 Python 自带的,也不是 conda 建环境时默认装的(除非你用了anaconda这个 meta 包),必须手动装。conda activate proj-a pip install ipykernel # 或者 conda install ipykernel装完之后再注册 kernelspec,顺序不能反。我见过有人先注册再装 ipykernel,结果注册时用的是旧状态,kernel.json 里指向的模块不存在,照样死。4.2 版本冲突:ipykernel 和 jupyter_client 打架比"没装"更隐蔽的是"装了但版本不兼容"。ipykernel依赖jupyter_client,而jupyter_client又依赖pyzmq。这三个包的版本如果对不上,内核启动时会在建立 ZMQ 连接那一步挂掉,报错信息往往是一堆 traceback,最后以 exit code 1 结束。典型症状是:手动跑python -m ipykernel_launcher -f xxx.json时,看到类似ImportError: cannot import name 'xxx' from 'jupyter_client'或者zmq.error.ZMQError。这时候不要一个个去猜版本,直接用 pip 的依赖解析来修:pip install --upgrade ipykernel jupyter_client pyzmq如果升级后还是不行,可能是某个包被 conda 和 pip 混装搞乱了。这种情况我建议在一个干净环境里重装,而不是在污染环境里修。因为 conda 和 pip 混用导致的依赖树错乱,修起来的时间成本远高于重建。4.3 用 conda 还是 pip 装 ipykernel这个问题没有绝对答案,但有个原则:如果你这个环境里的其他包主要是 conda 装的,那 ipykernel 也用 conda 装;如果主要是 pip 装的,那就用 pip。混着来最容易出问题,因为 conda 和 pip 各自维护一套依赖记录,互相不知道对方干了什么。我个人的习惯是:科学计算类环境用 conda 装核心包(numpy、scipy、pandas),ipykernel 也跟着 conda;纯 Web 或工具类环境用 pip,ipykernel 也 pip。这样依赖树相对干净,出问题的概率低很多。5. 端口、权限与路径里的暗雷5.1 ZMQ 端口被占用或无法绑定内核和前端之间通过 ZMQ 通信,需要绑定几个本地端口。如果这些端口被占用,或者系统限制导致无法绑定,内核启动就会失败。这种情况在 Windows 上尤其常见,因为 Windows 的端口回收比较慢,频繁重启内核时容易撞上。判断方法:手动启动内核时,如果看到OSError: [Errno 98] Address already in use或者zmq.error.ZMQError: Address already in use,那就是端口问题。解决办法有几个:等一会儿再试,让系统回收端口。重启 VS Code,释放它持有的连接。如果是 Windows,检查是否有残留的 python 进程占着端口,用任务管理器杀掉。# Linux/macOS 查看占用端口的进程 lsof -i :8888 # Windows netstat -ano | findstr :88885.2 工作目录不存在导致的启动失败这个坑非常隐蔽。内核启动时会尝试cd到 notebook 所在的工作目录。如果这个目录不存在(比如你删了文件夹但 VS Code 还开着那个 notebook),或者没有访问权限,内核进程会在初始化阶段直接退出,报 exit code 1。我遇到过一次:把项目文件夹从~/work/proj重命名成了~/work/proj-v2,但 VS Code 的工作区还指向旧路径,结果所有 notebook 全崩。排查时手动跑内核命令,发现它cd到一个不存在的目录,然后就没有然后了。解决办法:关掉 VS Code,重新打开正确的文件夹。如果 notebook 文件本身还在,用"打开文件夹"的方式重新加载,而不是直接双击 notebook 文件。5.3 路径里有中文或空格Windows 用户特别容易踩这个。如果你的 Python 环境路径、或者 notebook 所在路径里包含中文、空格、特殊字符,某些版本的 ipykernel 在拼接命令行参数时会出错,导致内核启动失败。这个问题的表现是:手动跑命令时看起来正常,但 VS Code 启动时就死。规避方法很简单:Python 环境路径和项目路径都用纯英文、无空格。比如D:\envs\proj-a和D:\work\proj-a,不要用D:\我的环境\项目 A。虽然现代工具对中文路径的支持越来越好,但在内核启动这种涉及多层进程调用的场景里,少一个变量就少一个坑。6. 一套可复用的排查流程6.1 从 Output 面板拿到真实命令前面提过,第一步永远是打开 Output 面板选 Jupyter,找到类似这样的日志:Starting kernel: /path/to/python -m ipykernel_launcher -f /tmp/kernel-xxx.json把这条命令完整复制出来。注意,有时候日志里会有多行,要找到最后实际执行的那条。如果日志里没有,可以在 VS Code 设置里把jupyter.logKernelOutput打开,它会打印更多细节。6.2 在终端里手动复现拿到命令后,在终端里跑一遍。这一步的关键是用和 VS Code 相同的 shell 环境。如果你在 VS Code 里用的是 PowerShell,那就在 PowerShell 里跑;如果用的是 bash,就用 bash。因为不同 shell 的环境变量加载逻辑不一样,可能影响 Python 的模块搜索路径。手动跑的时候,观察输出:如果报No module named 'ipykernel',去装 ipykernel。如果报ImportError或AttributeError,是版本冲突,去升级相关包。如果报Address already in use,是端口问题,去释放端口。如果报FileNotFoundError或PermissionError,是路径或权限问题。如果什么都没报,进程直接退出,那可能是kernel.json配置有问题,去检查 kernelspec。6.3 逐层验证的检查清单为了让你少走弯路,我把整个链路的检查点整理成一张表,按顺序排查即可:检查层检查内容命令/方法常见问题解释器VS Code 选中的解释器路径Python: Select Interpreter选错环境解释器终端里 python 的实际路径which python/where python与 VS Code 不一致包ipykernel 是否安装pip show ipykernel新环境漏装包依赖版本是否兼容pip checkjupyter_client 冲突kernelspec是否注册jupyter kernelspec list未注册或路径失效kernelspeckernel.json 里的 argv查看对应目录下文件路径写死且已失效端口ZMQ 端口是否可用lsof -i/netstat -ano端口被占用路径工作目录是否存在ls/dir目录被删或改名路径是否含中文/空格目测特殊字符导致解析失败按这个表从上往下走,基本能在十分钟内定位到问题层。我自己的经验是,前四行能解决八成问题,后面几行属于偶发但一旦遇到就很折磨的类型。6.4 重建环境:最后的杀手锏如果排查了一圈还是没头绪,或者环境已经被 conda/pip 混装搞得千疮百孔,那就别修了,直接重建。重建的步骤:conda create -n proj-new python=3.11 conda activate proj-new pip install ipykernel pandas numpy matplotlib python -m ipykernel install --user --name proj-new --display-name "Python (proj-new)"然后重启 VS Code,选新环境。整个过程十分钟以内,比在一个坏环境里折腾两小时划算得多。我现在的原则是:环境一旦出现无法解释的内核崩溃,且排查超过二十分钟,直接重建。时间比什么都贵。7. 几个我实际踩过的真实场景7.1 场景一:conda 环境重命名后全崩有一次为了统一命名规范,我把几个 conda 环境从env1、env2改成了proj-data、proj-model。改完之后,所有 notebook 全部报 kernel died。手动跑内核命令,发现kernel.json里的argv还指向旧路径env1/bin/python,而这个路径已经不存在了。解决方式就是删掉旧 kernelspec 重新注册。但这里有个细节:jupyter kernelspec remove用的是--name参数,也就是注册时的内部标识,不是显示名。如果你忘了内部标识是什么,可以去 kernelspec 目录下看文件夹名,那个就是。7.2 场景二:Windows 下路径含空格导致启动失败某次在 Windows 上,Python 装在C:\Program Files\Python311,路径里有空格。VS Code 启动内核时,命令行参数拼接没有正确加引号,导致ipykernel_launcher收到的-f参数被截断,内核启动失败。手动在终端里跑同样的命令却正常,因为终端会自动处理引号。这个问题的根因在扩展的参数拼接逻辑上,用户层面能做的就是把 Python 装到无空格路径,比如C:\Python311。虽然听起来很原始,但确实能绕开这类问题。7.3 场景三:jupyter_client 版本过新导致不兼容有一次手贱pip install --upgrade jupyter_client,升到了最新版,结果 ipykernel 还是旧版,两者 API 对不上,内核启动时报AttributeError。这种"升级了一个包但没升级配套包"的操作,是版本冲突的经典来源。教训就是:升级 Jupyter 相关包时,要么一起升,要么都别动。如果只想升一个,先看看它的依赖关系,确认配套包是否兼容。pip check可以帮你发现这类问题,但它只在安装后检查,启动时的问题还得靠手动复现来暴露。8. 把坑填上之后的一些习惯踩了这么多次之后,我养成了几个习惯,分享出来供参考。第一,新建环境后第一件事就是装 ipykernel 并注册 kernelspec,不要等到写 notebook 时才想起来。第二,环境路径和项目路径一律用英文无空格,从源头上规避路径解析问题。第三,不轻易混用 conda 和 pip 装同一个环境的包,如果非要混,先装 conda 的,再用 pip 补缺。第四,遇到内核崩溃先看 Output 面板再手动复现,不要盲目重启,重启解决不了根因。还有一个很实用的小技巧:在 notebook 的第一个 cell 里跑import sys; print(sys.executable),确认当前内核用的到底是哪个 Python。这个习惯能帮你在一开始就发现环境错位,而不是等到跑了一半才崩。我现在的每个 notebook 第一个 cell 都是这行,已经成了肌肉记忆。内核崩溃这件事,说到底就是链路太长、信息被吞、环境太乱三个因素叠加。把链路拆开、把信息捞出来、把环境理干净,exit code 1 就不再是玄学,而是一个有明确原因的普通报错。希望这篇记录能帮你少走几个我走过的弯路。