写这篇东西的起因,是我在社群里又双叒叕看到有人在问:“为什么我 conda activate 切到了某个虚拟环境,JupyterLab 里 import 的还是 base 环境的包?”或者是“JupyterLab 里怎么才能看到我新创建的那个 conda 环境?”说实话,这个问题几乎每隔几天就会出现一次,而且每次版本更新、每次有人换了新电脑,它就会重新冒出来。今天干脆把它彻底讲透,把 JupyterLab 和 Anaconda 虚拟环境之间的那层“窗户纸”捅破,顺便把我这几年实际踩过的坑和总结出来的经验一起放进来了。
先说明白一个最关键的前提:JupyterLab 里你是看不到“conda 环境”这个概念的。你只能看到kernel。我们做的一切操作,本质上是把 conda 环境“包装”成一个 Jupyter 能识别的 kernel,然后在 JupyterLab 的界面里去切换这些 kernel,从而间接实现“切换 Python 虚拟环境”的效果。搞不懂这一点,后面所有操作都会觉得莫名其妙。
先说说文章适合谁:刚装了 Anaconda、正在被环境问题折磨的 Python 新手;以及用 JupyterLab 做数据分析、机器学习,发现环境切换总是不对劲的老手。这篇不是速查清单,我会把原理和命令一起讲,看完你不仅能照着做,还能明白“为什么要这么做”。
1. 先理解为什么 JupyterLab 看不到你的 conda 环境
1.1 核心概念:JupyterLab 只认 kernel,不认 conda
很多人第一次接触 Anaconda 时,心里会建立一个隐含的模型:conda 可以创建多个虚拟环境,每个环境里有一套独立的 Python 解释器和第三方库;那我只要在终端里conda activate 某个环境,整个系统——包括 JupyterLab——就应该跟着走。这个模型在命令行场景下基本成立,但在 JupyterLab 这个场景下,它完全是错的。
原因在于 JupyterLab 的架构设计。JupyterLab 本身只是一个 Web 前端,负责渲染 Notebook 界面、管理文件、展示输出。真正执行 Python 代码的,是一个独立的kernel 进程。你每打开一个 Notebook,JupyterLab 就会根据这个 Notebook 关联的 kernel 规范(kernel spec)去启动一个 Python 解释器进程,这个进程才是真正跑代码的“工人”。
问题来了:kernel 不是凭空出现的,它必须提前被“注册”到 JupyterLab 能发现的位置。JupyterLab 启动时,会去系统几个固定的目录里寻找所有可用的 kernel 规范,包括用户目录下的~/Library/Jupyter/kernels(macOS)、%APPDATA%\jupyter\kernels(Windows)、~/.local/share/jupyter/kernels(Linux),还有 Anaconda 安装目录下的share/jupyter/kernels等。它只会列出这些目录里注册过的 kernel,至于你有多少个 conda 环境、conda 环境叫什么名字,JupyterLab 根本“不知道”。
用一个生活化的比方:conda 环境是一群住在不同小区的“工人”,JupyterLab 是一个“招工平台”。平台不会自动知道每个小区里有哪些工人,只有工人们到平台登记过(注册 kernel),平台才能在页面上显示出来供你选择。我们后面要做的,就是帮各个 conda 环境里的 Python 解释器进行“登记”。
1.2 为什么 conda activate 切换不了 JupyterLab 的 Python 版本?
这就解释了另一个常见误区:很多人以为在终端里conda activate 环境A,然后再启动jupyter lab,JupyterLab 里就跑的是环境A 的 Python。这个认知有一半是对的,但很容易踩坑。
如果你是在 Anaconda 的基础环境(base)里执行conda activate 环境A,然后继续在这个终端窗口里输入jupyter lab,那么启动 JupyterLab 的这个 Python 进程确实是环境A 的 python,但 JupyterLab 页面里新建的 Notebook,默认用的 kernel 仍然是 JupyterLab 自己找到的第一个可用 kernel——绝大多数情况下是 base 环境注册的那个python3kernel。页面里代码用的解释器,和启动 JupyterLab 的进程用的解释器,是两码事。
这就像你开了一家餐馆(启动 JupyterLab),餐馆装修时用的工人是环境A 的人(启动进程),但后厨真正炒菜的厨师(kernel)你根本没换,还是原来那批(base 的 kernel)。所以很多同学切了半天环境,发现 Package 版本怎么都变不了,就是这个原因。
正确的心智模型是:一个 conda 环境要想在 JupyterLab 里被使用,必须事先把它注册为一个 kernel。注册之后,你可以在 JupyterLab 的 Launcher 界面的 Notebook 列表里看到它的名字,或者在已有 Notebook 的 Kernel 菜单里随时切换。切过去之后,代码由该环境的 Python 解释器执行,包列表自然也就是那个环境里的包。
1.3 所有方案的底层逻辑
既然搞清楚了原理,我们就可以把目标拆成两件事:第一,让目标 conda 环境具备成为一个 Jupyter kernel 的能力;第二,让 JupyterLab 能在启动时发现这个 kernel。只要这两个目标达成,JupyterLab 里切换环境就是一个下拉菜单的事。
实现的路径不止一条:
- 手动在每个目标环境里安装
ipykernel,然后用python -m ipykernel install的方式注册 kernel。这是最经典、最可控、也最适合理解原理的方法,我强烈建议新手至少亲手做一遍。 - 在 base 环境里安装
nb_conda_kernels插件,让它自动扫描并暴露所有 conda 环境。这种方式一劳永逸,不用每个环境都手动注册,适合环境很多的场景。 - 不注册 kernel,直接在激活某个环境后用该环境里的 Python 启动一个全新的 JupyterLab 服务。这个方式简单直接,但和“在一个 JupyterLab 里切换环境”是完全不同的体验。
下面我依次展开,你根据自己情况选方案。我个人建议先把方案一亲手跑通,理解之后再上方案二。
2. 动手前的环境确认
2.1 确认 Anaconda 版本与 JupyterLab 是否已安装
在开始注册 kernel 之前,先把底子摸清。打开终端(Windows 下建议用 Anaconda Prompt,macOS/Linux 用普通终端即可),分别跑一下下面几条命令:
conda --version conda env list jupyter lab --version第一条命令确认 conda 本身是可用的;第二条命令列出你当前机器上所有的 conda 环境,后面注册 kernel 时要用到环境名;第三条命令确认 JupyterLab 已安装且版本正常。如果你还没装 JupyterLab,在 base 环境里执行:
conda install -n base jupyterlab -c conda-forge这里我习惯把 JupyterLab 装在 base 环境,而不是每一个环境里都装一份。因为 JupyterLab 是前端工具,它只需要一份就够了;前端要连接的 kernel(也就是各种 Python 环境)才是真正需要分散部署的“后厨人员”。这个策略能省不少磁盘空间,也避免了版本混乱。
另外提醒一点:新版 Anaconda 默认自带的可能是 Jupyter Notebook 而不是 JupyterLab,但现在 JupyterLab 才是主流,如果发现jupyter lab命令不存在,直接用上面那条命令补上即可。JupyterLab 3.x 和 4.x 的操作差别不大,本文命令在这两个大版本下都可用。
2.2 列出环境并确定目标环境名
确认完成后,看conda env list的输出,格式大概是这样的:
# conda environments: # base * /opt/anaconda3 data_analysis /opt/anaconda3/envs/data_analysis deeplearning /opt/anaconda3/envs/deeplearning第一列是环境名称,第二列是该环境 Python 解释器所在的绝对路径。带星号*的是当前终端里激活的环境。这里我要强调一个很有用的认知:环境名本质上只是 conda 给某个目录起的别名,真正决定“这是哪个环境”的是它后面那串路径。后面你如果手工改 kernel.json,理解了路径就理解了所有问题。
下一步,明确你希望 JupyterLab 里出现哪些环境。比如我有data_analysis和deeplearning两个环境,分别是数据分析场景和深度学习场景,那我的目标就是让这两个名字出现在 JupyterLab 的 kernel 列表里。
2.3 理解 ipykernel 的角色
在正式操作前,还有最后一个概念需要讲清楚:ipykernel是什么,为什么它必不可少。
ipykernel是 Jupyter 生态里负责“翻译”的组件。JupyterLab 前端通过 HTTP/WebSocket 和 kernel 进程通信,向前端发送的是 JSON 格式的交互协议消息;Python 解释器本身不会说这种“语言”,而ipykernel就是在 Python 解释器里装的一个“翻译官”,它把 Jupyter 协议消息翻译成 Python 代码执行的指令,再把执行结果翻译回 JSON 消息发给前端。
打个比方:前端是中国的老板,Python 解释器是只会说 Python 的外籍厨师,ipykernel就是那个双方都听得懂的翻译员。没请翻译,老板(JupyterLab)就算看到了厨师(conda 环境)也没法让他干活。
所以,每一个你想在 JupyterLab 里使用的 conda 环境,都必须装上ipykernel。这一步不能省,也不是良性可选项。
3. 最常用的方案:给每个环境手动注册 kernel
3.1 激活目标 conda 环境
现在开始正式操作。打开终端,激活你想要注册的那个 conda 环境:
conda activate data_analysis激活完成后,命令行提示符前面会出现(data_analysis)字样。这一步的意义是让后续所有操作都在该环境的上下文里执行。这里有一个 Windows 用户特别容易踩的坑:如果你在 cmd 或 PowerShell 里执行conda activate报错,说这个命令不存在,那是因为你还没执行过conda init。解决办法是在 Anaconda Prompt 里先跑一次:
conda init然后重新打开一个终端窗口即可。这是 Anaconda 安装后最常见的初始化问题,没有之一。
3.2 在环境内安装 ipykernel
激活环境后,安装ipykernel。我强烈推荐用 conda 安装而不是 pip,尤其是在 Windows 上:
conda install ipykernel如果网络不太好,可以先给 conda 配置国内镜像源再装,比如清华源或中科大源。用 conda 而不是 pip 的理由有两个:第一,conda 会一并解决ipykernel依赖的底层库(比如pyzmq这类编译型 Python 包)的二进制文件问题,避免 pip 在某些系统上还要现场编译导致报错;第二,conda 安装会把包完整放进当前环境指定的site-packages里,不会出现 pip 把包装错位置的诡异情况。
你可能会问:“那我之前没在这个环境里装过 Jupyter 生态的任何东西,直接装 ipykernel 行不行?”答案是完全可以。ipykernel不自带前端界面,它只提供 kernel 能力。你不需要在每个环境里重复安装 JupyterLab,这也再次印证了前面说的“前端只留一份”策略。
3.3 执行 kernel 注册命令
装好ipykernel之后,在这个已激活的环境里执行注册命令:
python -m ipykernel install --user --name data_analysis --display-name "Python (data_analysis)"我来逐个拆解这几个参数,搞清楚它们,你以后就不会对这条命令感到陌生了:
python -m ipykernel:表示调用当前环境下 Python 解释器中的ipykernel模块。注意这里一定是在激活目标环境后执行,确保用的是目标环境里的python解释器。install:安装一个 kernel 规范到本机。--user:将 kernel 规范安装到当前用户的 Jupyter 配置目录,而不是系统级目录。加了--user,不需要管理员/root 权限,而且只对当前用户生效,最安全。如果省略,在 Anaconda 安装目录具备系统写权限时也可能写到Anaconda/share/jupyter/kernels下,但这可能污染全局配置,一般不推荐。--name:这是 kernel 的内部标识名,Jupyter 会用它作为目录名存放 kernel 配置。name 建议全小写、用下划线或连字符连接,不要含空格和中文,因为它在底层就是文件夹名。最方便的做法是直接等于 conda 环境名,见名知意。--display-name:这是 JupyterLab 界面上显示出来的名字,你可以随便起,中文也支持。写成Python (data_analysis)这种格式,新打开 Launcher 页面时,你就会在 Notebook 的图标下看到这个名字,一目了然。
执行完毕,终端会提示Installed kernel spec data_analysis in ...这样一行路径信息。按同样的流程,把你想用的其他环境也依次注册一遍。
3.4 在 JupyterLab 里验证效果
注册完成后,回到 JupyterLab 页面。这里有个细节要特别注意:如果你在注册 kernel 之前就已经打开着 JupyterLab,页面通常不会自动刷新出新的 kernel,因为 JupyterLab 在启动时会建立一次 kernel 列表的缓存。所以此时你需要刷新浏览器页面(建议Ctrl+Shift+R强制刷新),或者干脆重启 JupyterLab 服务进程。
刷新之后你会在两个地方看到效果。第一个地方是 Launcher 页面:点击左上角+打开新的 Launcher,在 Notebook 区域会出现一个Python (data_analysis)的图标,点击它新建的 Notebook 就运行在data_analysis环境的 Python 解释器上。第二个地方是已有 Notebook 的右上角或菜单栏:你可以在已打开的 Notebook 里,通过Kernel菜单 →Change Kernel…,在弹窗里把当前 Notebook 切换到你刚刚注册的Python (data_analysis)。切换后,如果代码里之前有过执行结果,最好重启一下内核(Kernel→Restart Kernel and Run All Cells)让所有变量重新加载,否则会出现变量是上一套环境产生的、代码却在新环境下执行这种别扭状态。
验证做得对吗?在 Notebook 的第一格输入以下代码运行一下:
import sys print(sys.executable)如果输出的路径指向/opt/anaconda3/envs/data_analysis/bin/python(Windows 下是...\envs\data_analysis\python.exe),那说明你已经成功把该环境接到了 JupyterLab 上,恭喜,最核心的问题已经解决了。
4. 一劳永逸的方案:用 nb_conda_kernels 自动注册所有环境
4.1 nb_conda_kernels 是怎么帮你干活的
手动注册的方式直观,但有个现实问题:如果你有七八个环境,每个环境都要激活、装 ipykernel、执行注册命令,重复劳动不说,后面新建环境还得记得再来一遍。有没有办法让 JupyterLab 自动识别所有 conda 环境?答案是有的,那就是nb_conda_kernels插件。
这个插件的使用方式非常反直觉但也非常优雅:你只需要在 base 环境里安装它,然后它会去扫描器上所有 conda 环境,自动为那些“能作为 kernel 运行的环境”生成内核入口。换句话说,它干了你本来要手动重复 N 遍的活儿。安装命令:
conda install -n base nb_conda_kernels -c conda-forge安装完成后,重启 JupyterLab。你再打开 Launcher 或者切换 kernel 时,会发现列表里自动多出了所有 conda 环境的名字。
这里要讲一下它的“识别”机制,不然你会困惑为什么有些环境出现、有些环境不出现。nb_conda_kernels并不是无脑列出所有环境,它会检查每个环境里是否安装了ipykernel。如果一个环境连ipykernel都没有,它无法把该环境当作一个可用的 Python kernel 来注册,这种情况下你就会发现列表里缺了某个环境。
所以完整的流程是:先在 base 里装好插件,然后对你需要的每个 conda 环境,都要激活并执行conda install ipykernel。注意,装了插件之后这一步依然不能省,“是否具备 ipykernel”是插件识别环境的开关。但好处是:你不用再手动执行python -m ipykernel install注册命令了,插件会替你批量处理。
4.2 nb_conda_kernels 的最佳实践与注意点
这种方案适合什么样的场景?我认为最适合那些环境较多、且经常增删环境的重度用户。比如我同时维护数据清洗、机器学习、深度学习、爬虫四个环境,用这个插件,我只需要管好每个环境里有没有 ipykernel,新增环境时也只需装个 ipykernel 就完了,JupyterLab 重新刷新一下,新环境自动出来。
用这个方案有几点教训,是很多教程不会提的:
第一,插件装好后,建议把 JupyterLab 的服务进程彻底停掉再重启,而不是只刷新浏览器。有些版本里,kernel 列表的刷新依赖服务端重新扫描,光在浏览器里强制刷新不够。
第二,环境命名为英文小写字母加下划线,别用中文。虽然 nb_conda_kernels 能识别,但中文环境名在某些情况下会在内部路径处理上出现编码问题,尽管现在新版好很多,但何必给自己添麻烦。
第三,如果你后来删除了某个 conda 环境,JupyterLab 里的对应 kernel 条目应该也会随着扫描消失。万一没消失,重启一次 JupyterLab 服务进程就干净了。
第四,用 nb_conda_kernels 之后,kernel 的显示名称一般是Python [conda env:环境名]这种格式。习惯了就很好认,但我个人觉得不如手动注册时设置的Python (data_analysis)看起来干净。当然这只是审美问题,不影响功能。
5. 另外两种应急情况的操作思路
5.1 直接在激活环境中启动独立的 JupyterLab 服务
有一种常见需求是你不想轻易改动太多环境、只是想临时在这个环境里跑一下代码并看看效果。这时最简单粗暴的方法是激活该环境,然后在该环境中直接启动 JupyterLab:
conda activate deeplearning pip install jupyterlab ipykernel jupyter lab这样启动的 JupyterLab 服务完全运行在deeplearning环境里,默认的新建 Notebook 也会使用该环境的 Python。页面里依然只有一个python3kernel,但这个python3指向的就是当前环境的解释器。
这个方式的限制很明显:第一,每次要切环境,都得重启 JupyterLab 服务,无法在同一个页面上切换;第二,该环境里必须安装了 JupyterLab 本体,这和我们“前端只保留一份”的策略相悖,多装一份就多占一份空间、多一份版本同步负担。所以我一般只在排查问题(比如怀疑是 JupyterLab 前端版本导致的问题)时才用这个方式,日常使用不推荐。
5.2 手动改 kernel.json 指向任意 Python 解释器
还有一种场景是:某个 conda 环境因为各种原因,你已经没法在里面成功安装 ipykernel 了,但你就是想用它的解释器。这时候可以手工创建一个 kernel 规范,直接指定解释器路径。
先看看当前已有的 kernel 规范和它们的目录位置:
jupyter kernelspec list输出类似:
Available kernels: python3 /opt/anaconda3/share/jupyter/kernels/python3 data_analysis /Users/yourname/Library/Jupyter/kernels/data_analysis每个 kernel 对应的目录里有一个kernel.json文件,它定义了启动 kernel 时执行的命令。你可以自己新建一个目录并手动写一个kernel.json,内容模板如下:
{ "argv": [ "/opt/anaconda3/envs/deeplearning/bin/python", "-m", "ipykernel_launcher", "-f", "{connection_file}" ], "display_name": "Python (deeplearning_manual)", "language": "python", "metadata": { "debugger": true } }其中argv第一项改成目标环境里 Python 解释器的绝对路径。macOS/Linux 路径一般在envs/环境名/bin/python,Windows 在envs\环境名\python.exe。保存后重启 JupyterLab,就能看到你手工定义的那个 kernel 了。
但请注意:即使手工指定了解释器路径,Jupyter 在启动 kernel 时仍会尝试通过ipykernel_launcher这个入口拉起内核。也就是说这个环境里必须在 site-packages 中存在完整的ipykernel包,否则依然会启动失败。手工改 json 解决不了“环境里没有 ipykernel”这个根本问题,它更适合的是那些 ipykernel 存在但 kernel 注册信息损坏的场景,比如说你把环境目录搬迁过、原 kernel 路径失效了。所以这个方案我给它的定位是“修复手段”而不是“常规通道”。
6. 高频问题排查与避坑记录
6.1 常见问题速查表
把这几年来看到的高频问题整理成一张表,按“先看现象、再查原因、最后解决”的顺序来组织:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 注册完 kernel 后 JupyterLab 里看不到 | JupyterLab 服务端缓存了旧的 kernel 列表 | 强制刷新浏览器;若无效,彻底重启 jupyter lab 进程 |
| kernel 列表有名字,但点开一直显示启动失败 | 该环境的 ipykernel 版本不兼容或缺失 | 激活该环境后conda install --force-reinstall ipykernel,重启 JupyterLab |
| 选了环境名,但 import 的包还是 base 环境的 | 注册时没有在目标环境里执行命令,或环境名标识错误 | 用sys.executable打印解释器路径检查;确认注册命令是在激活环境后执行 |
| 在 Windows 上 conda activate 报错 | conda 尚未初始化 shell | 在 Anaconda Prompt 里执行conda init后重新打开终端 |
| 删除 conda 环境后 JupyterLab 还残留条目 | kernel 规范文件仍然留在 kernels 目录 | 执行jupyter kernelspec remove 环境名 |
| nb_conda_kernels 装好但某些环境不出现 | 目标环境里没有安装 ipykernel | 逐个环境激活后安装 ipykernel |
| pip 包装了但 notebook 里 import 提示不存在 | notebook 当前选中的 kernel 不是该 pip 包所在的环境 | 检查 kernel 选择;确认 pip 是在正确环境里执行 |
| 环境解释器路径变更导致 kernel 启动失败 | 比如 Anaconda 目录被移动过,kernel.json 里还是旧路径 | 用jupyter kernelspec list找到 kernel 目录,更新kernel.json中的路径 |
写清楚之后,我再补充几个排查逻辑上的建议。遇到 kernel 相关的问题,不要急着搜报错,第一步永远是确认“当前 notebook 到底用的哪个解释器”。直接在 notebook 里运行:
import sys print(sys.executable)这一步能定位一半以上的问题。如果打印出来的路径不是目标环境的 python,那环境切换就是失败的,后面所有包的行为都会对不上,这时候再回头看是不是注册、激活、选择这哪步出了问题。
6.2 几条实实在在的实操心得
最后分享几条比较个人的经验,都是踩过坑换来的。
第一,环境名和 kernel 名尽量统一。我有一个习惯,新创建一个 conda 环境时,环境名就叫data_analysis,注册 kernel 时--name data_analysis,这样我在终端和 JupyterLab 两边的认知是一致的。JupyterLab 界面里display-name可以写得冗余一点、友好一点,那是给别人看的;name和路径是给机器看的,一定要简洁规范。
第二,Windows 用户特别注意,别在 PowerShell 的默认蓝色窗口里执行conda install ipykernel之后,又跑到普通 cmd 里去启动 JupyterLab。不同的 shell 环境变量加载方式不同,容易造成“明明装了为什么找不到”的假象。统一用 Anaconda Prompt,或者把一个 shell 用到底,别来回切换。
第三,如果你使用 VS Code 里的 Jupyter 插件,那它的 kernel 管理逻辑和 JupyterLab 是共享同一套 kernel 注册机制的。你在命令行里注册好的 kernel,VS Code 里通常也能直接选到,不用重复操作。
第四,关于包的安装策略。我强烈建议数据分析、机器学习类的包尽量用 conda 安装而不是 pip,因为 conda 能自动处理非 Python 的二进制依赖(比如 CUDA 相关的库、MKL、OpenBLAS)。有时候你在 JupyterLab 里选了正确的环境,却还是会遇到import numpy报错缺少某些底层库,十有八九是当初用 pip 硬装的包没带上系统级依赖。这是 conda 环境管理里数一数二的大坑。
第五,jupyter kernelspec list和jupyter kernelspec remove是排查时最好用的两个命令。前者查看当前注册了哪些 kernel 以及路径;后者可以把注册坏掉的 kernel 规范清理掉。操作不可逆,所以用remove的时候要确认你删的就是那个要废弃的 kernel。
我在实际使用中的习惯是:所有环境统一在初始创建时就装好 ipykernel,再在 base 里装一个 nb_conda_kernels 作为兜底,双保险。手动注册那套方法我虽然讲得很细,但现在主要用来做排查和应急,日常靠插件自动扫描更省心。如果你刚上手,我建议先把第三节的手动方案完整跑通一次,再做第四节的一劳永逸方案,因为原理清楚之后,效率工具才不容易变成黑盒子。你被 JupyterLab 环境切换折磨过吗?按上面的步骤试完,大概率能干干净净地把环境给你理顺了。