尧图精选

Conda 新环境 pip list 多出包:sys.path 与 PYTHONPATH 排查

🕒 发布时间:2026/10/1 8:53:49 📁 来源:尧图网络
1. 先把现象钉死新环境里那些包到底从哪冒出来的“用 Conda 创建了一个空虚拟环境结果 pip list 一敲屏幕上刷刷刷列出四五十个包”——这个场景我见过太多次了帮同事排查也不下十回。先把最反直觉的结论摆在前面**conda create 出来的虚拟环境从来就不是真正意义上的“空”而 pip list 读到的东西也未必全部属于这个环境。**这两件事叠在一起才造成了“明明什么都没装却什么都有”的错觉。我自己最早踩这个坑是在一台装了 Anaconda 全量版的台式机上。当时想给一个爬虫小项目建个干净环境命令敲得很标准conda create -n spider python3.9 conda activate spider pip list结果列表里出现了 numpy、pandas、scipy、jupyter、notebook、matplotlib……几乎把科学计算全家桶都端上来了。那时候我的第一反应是“conda 是不是把 base 环境的包全继承过来了”于是 conda remove 一通乱删删完发现 pip list 还是老样子甚至 pip 本身都不太正常了。后来冷静下来一看sys.path才发现问题根本不在 conda 的继承机制上。所以这篇东西我想按排查的真实顺序来写先弄清楚 pip list 到底在读哪些目录再回头看 conda 建环境时干了什么最后落到环境变量这种最容易被忽略的地方。整套流程走下来你不仅能修好眼前这个问题以后再遇到“包莫名其妙多出来”“pip 装的东西在别的环境里也能看到”这类怪事基本都能自己定位。这篇适合三类人看刚接触 conda 和虚拟环境、被 Anaconda 全量安装搞晕的新手手里有好几个项目、环境互相串味的老手以及需要在多台机器上复现同一套环境的工程同学。不需要你提前懂 sys.path 或者 site-packages我会从最基础的原理讲起。2. 追根溯源pip list 究竟在扫描哪些目录2.1 sys.path 是唯一裁判想搞明白 pip list 为什么能列出那么多包得先知道它从哪儿拿数据。pip 本身不维护任何“已安装包”的清单文件它是实时扫描 Python 解释器的 sys.path 里所有 site-packages 目录读每个包目录下的 .dist-info 或 .egg-info 元数据然后拼出这份列表。也就是说pip list 的输出完全等价于一句话当前解释器能 import 到的、带元数据的包有哪些。这一点非常关键。很多人以为 pip list 读的是某个环境的登记表其实不是。它读的是目录。你把哪些目录塞进了 sys.pathpip 就会把那些目录里的包全部算进来。所以“环境不干净”这个问题的本质从来不是环境本身脏而是这个环境的解释器看到了它不该看到的目录。你可以在任意环境里执行这一句把真相直接摊开python -c import sys; print(sys.executable); [print(p) for p in sys.path]第一行输出的是当前 python 可执行文件的绝对路径后面几行是完整的搜索路径。如果这里面出现了 base 环境的 site-packages、出现了用户目录下的 .local那 pip list 多出来的包来源就找到了。2.2 python -m site 输出的四个关键字段比手写脚本更省事的是直接调site模块它在所有标准 Python 里都自带python -m site输出的内容里有四个字段值得盯死我按重要性排一下sys.path最终生效的搜索路径列表一切结论以此为准。USER_BASE用户级安装的根目录Linux/macOS 上一般是~/.localWindows 上是%APPDATA%\Python。USER_SITE用户级 site-packagesLinux 上通常是~/.local/lib/python3.x/site-packages。ENABLE_USER_SITE布尔值决定要不要把 USER_SITE 挂进 sys.path。这个字段是 True 的时候你所有同版本 Python 的环境都会共享 USER_SITE 里的包。我遇到过最典型的一次是同事在 base 环境里手滑敲了pip install --user requests之后他新建的每一个 Python 3.9 环境里都能看到 requests删环境重装都没用。原因就在 ENABLE_USER_SITE 为 TrueUSER_SITE 被挂进了每个环境的 sys.path。这种坑跟 conda 一点关系都没有纯粹是 Python 自身的用户级安装机制。2.3 一个反直觉的事实pip 和 python 可以不是一家人还有一个高频误判你在环境里敲pip list但这条 pip 命令指向的可能是 base 环境的 pip。这时候它扫描的是 base 的 site-packages列出来的自然是一大堆包而你误以为是自己新环境里的。判断方法很简单永远先看这两行which python # Windows 用 where python which pip # Windows 用 where pip pip -Vpip -V输出的格式大致是pip 24.x from /path/to/python/site-packages/pip (python 3.x)。如果这个路径和你which python出来的路径不属于同一个环境目录那就是指错了。最稳妥的写法是永远用python -m pip代替裸pip这样调用的一定是当前 python 对应的 pip跨平台都不会出岔子。我现在的习惯是所有文档里写的安装命令都写成python -m pip install xxx。虽然多敲几个字符但能让“pip 装到别的环境去了”这类问题直接绝迹。3. conda 那一侧默认包、元包与 .condarc 的坑3.1 Anaconda 和 Miniconda 建环境时行为不一样先说结论**用 Anaconda 安装包装上来的 conda和用 Miniconda 装上来的 condaconda create -n xxx python3.x的实际行为可能不一样。**差别不在 conda 本体而在于安装器写进~/.condarc的默认配置。Miniconda 一般是纯净的创建环境时只会装上 python 本身以及 pip、setuptools、wheel 这几个最小依赖。而某些 Anaconda 的发行版本或者企业定制镜像会在.condarc里预置create_default_packages这一项值是[python, anaconda]或者类似的东西。一旦这一项存在你每建一个新环境conda 都会自动把 anaconda 这个元包塞进去。“元包”这个词值得解释一下。anaconda 本身不是一个能 import 的库它是个空壳包作用只有一个把几百个包写成自己的依赖。装它等于一次性装完整个科学计算生态。所以 pip list 里冒出来的 numpy、scipy、jupyter 之类很可能就是它带进来的。查自己机器上有没有这个配置直接看conda config --show create_default_packages conda config --show-sources第二条会把所有生效的 .condarc 文件路径和内容列出来包括系统级的、用户级的、环境级的。如果看到 create_default_packages 非空那就是它了。3.2 create_default_packages 这条配置发现之后怎么处理取决于你到底想不想要这个行为。我个人建议是清掉理由很直白虚拟环境的价值就在于隔离和最小化一个默认带四十几个包的环境跟你直接在 base 里干活没有本质区别还白白占几百 MB 到几个 GB 的磁盘。清掉的方式有两种。临时方式是在创建时显式覆盖conda create -n myenv python3.11 --no-default-packages永久方式是把配置项改空conda config --remove-key create_default_packages改完之后可以用conda config --show create_default_packages确认一下输出是空列表。这里有个细节容易踩--no-default-packages这个参数在新版 conda 里依然有效但它是创建时生效的不会追溯已经建好的环境。已经建错的环境要么用conda remove -n myenv --all删掉重建要么手动conda remove一个个卸。前者更快也更干净。3.3 环境里的 pip 到底装了什么还有一种情况容易被误认为“conda 塞包”环境建好后你其实已经无意中装过东西了。比如某些 conda 包在安装时会触发 pip 的 post-link 脚本或者你自己在排查过程中敲过conda install pip、python -m ensurepip之类的命令。这些操作都有可能把额外的依赖拉进来。我一般的验证套路是环境刚建好、还没跑任何安装命令之前立刻记一次“基线”conda activate myenv python -m pip list --formatfreeze baseline.txt cat baseline.txt干净的 Python 3.11 环境baseline 里通常只有四到五行pip、setuptools、wheel偶尔加上 conda 自己的 certifi 或 openssl 之类的非 Python 依赖这些不会出现在 pip list 里。如果你在这一步就看到了几十行那问题一定出在环境之外直接跳到第 4 节。4. 环境变量那一侧PYTHONPATH 和用户级 site-packages4.1 PYTHONPATH 是最隐蔽的污染源如果说 create_default_packages 还算明面上的配置那 PYTHONPATH 就是纯粹的地下工作者。它的作用是在解释器启动时把指定的目录插到 sys.path 的最前面。这意味着无论你在哪个 conda 环境里只要这个变量存在那些目录里的包都会出现在你的 import 路径里。PYTHONPATH 之所以讨厌是因为它经常不是你主动设的。我见过几种典型来源装某个大型工程软件时安装程序顺手往系统环境变量里写了 PYTHONPATH指向它自带的 Python 库目录。某些 IDE 的旧版本在启动调试时会注入 PYTHONPATH。早期为了图省事手动设过一次后来忘了。Windows 上某些批处理脚本 set 完没有清理。排查命令三平台通用# Linux / macOS echo $PYTHONPATH # Windows CMD echo %PYTHONPATH% # Windows PowerShell echo $env:PYTHONPATH如果输出非空那基本可以锁定嫌疑。处理方式分两种临时在当前终端清掉unset PYTHONPATH/set PYTHONPATH/Remove-Item Env:PYTHONPATH或者到系统环境变量设置里永久删除。注意清完之后要重新开一个终端因为环境变量是在进程启动时读取的改完不重开不生效。注意清理 PYTHONPATH 之前先确认没有别的软件依赖它。有些工程仿真软件确实靠这个变量找自己的库删之前把值记下来必要时改成只在该软件的启动脚本里设置。4.2 ENABLE_USER_SITE 与 ~/.local 目录第二大类污染源是用户级 site-packages。Python 从很早就支持pip install --user把包装到用户目录而不是系统目录好处是不用管理员权限。代价是这些包会被同一 Python 小版本下的所有环境共享除非显式关掉。那个目录具体在哪直接问 Python 最准python -m site --user-siteLinux/macOS 上通常长这样/home/yourname/.local/lib/python3.11/site-packages。Windows 上通常在C:\Users\yourname\AppData\Roaming\Python\Python311\site-packages。进去看看里面有什么如果躺着一堆你熟悉的包那 pip list 多出来的东西就有出处了。处理方式有三种我按推荐程度排在环境里关掉用户级站点python -m pip config set global.user false或者在激活环境后设置环境变量PYTHONNOUSERSITE1。后者更彻底因为它是解释器层面的开关。清空那个目录确认没有软件依赖它的情况下把里面的包目录删掉。这个操作有风险建议先备份目录名列表。新建环境时避免使用同版本 Python 的用户站点这个方法不稳定不推荐因为 ENABLE_USER_SITE 是按 Python 版本判定目录的不是按环境。我自己更倾向第一种因为它是可逆的而且能写进激活脚本自动执行。在 conda 环境里可以在$CONDA_PREFIX/etc/conda/activate.d/目录下放一个脚本激活时自动 export PYTHONNOUSERSITE1。这样每次进环境都干净不用记。4.3 pip 的配置文件也能动手脚最后一类比较少见但确实存在pip 自己的配置文件里写了全局的 usertrue 或者 target 参数。pip 的配置读取顺序是全局/etc/pip.conf→ 用户~/.config/pip/pip.conf→ 环境$VIRTUAL_ENV/pip.conf。Windows 上对应%APPDATA%\pip\pip.ini和%USERPROFILE%\pip\pip.ini。一条命令就能看全python -m pip config debug它会列出所有被读取的配置文件路径、哪些存在、每个文件的生效内容。如果看到global.user true或者global.target /some/path那就是它在作怪。用python -m pip config unset global.user撤掉即可。顺便说一句很多人为了换国内源会写 pip.conf这是好事但写的时候容易顺手多加几行。每次改完配置跑一遍pip config debug确认最终生效项是个好习惯。5. 手把手从零搭一个真正干净的 conda 环境5.1 第一步清点家底在动手建环境之前先花两分钟把当前机器上的情况摸清楚。这一步做过之后后面出问题能少绕很多路conda --version conda info conda config --show-sources python -m pip config debug python -m site重点看三样conda 版本、.condarc 的来源和内容、当前 python 的 USER_SITE 和 ENABLE_USER_SITE。同时把当前终端的环境变量里跟 Python 相关的过一遍PYTHONPATH、PYTHONHOME、PYTHONSTARTUP这三个是重点排查对象。关于 PYTHONHOME 多说一句这个变量比 PYTHONPATH 更危险它会直接改变解释器认定的标准库位置。如果它指向了别的 Python 安装你的 conda 环境可能根本起不来或者起来之后加载的是另一套标准库。正常情况下它应该是空的不为空的话优先清掉。5.2 第二步创建环境确认过 .condarc 里没有 create_default_packages 之后创建命令可以写得很明确conda create -n cleanenv python3.11 --no-default-packages -y这里--no-default-packages属于双保险即使 .condarc 里还有残留配置这一条也会把它压住。-y只是省掉确认交互方便写脚本。如果你确实需要几个常用包建议在创建时用 conda 装而不是创建完再用 pip 装。原因是 conda 能处理二进制依赖比如 MKL、CUDA 运行库pip 处理不了。写法是conda create -n cleanenv python3.11 numpy pandas --no-default-packages -y这样装进来的包在 conda 的包管理系统里有记录conda list和pip list都能看到后续升级、回滚都干净。5.3 第三步激活并做一次体检创建完立刻激活然后做一次完整体检conda activate cleanenv python -c import sys; print(sys.executable) python -c import sys; [print(p) for p in sys.path] python -m site python -m pip list判断标准很直接sys.executable的路径应该指向envs/cleanenv/下面的 pythonsys.path里不应该出现别的环境的 site-packages也不应该出现.local的路径pip list的行数应该是个位数。这一步如果发现异常直接回到第 4 节对号入座不用往下走。把体检这一步养成习惯比事后删环境重装省事得多。5.4 第四步换源提速环境干净了接下来装包。国内环境下默认 PyPI 源的速度确实难受换源几乎是标配。conda 和 pip 是两套独立的源配置得分别处理。pip 换源用一条命令就够它会自动写到用户级配置文件python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple python -m pip config set global.trusted-host pypi.tuna.tsinghua.edu.cnconda 换源稍微复杂一点因为要同时指定好几个 channelconda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free conda config --set show_channel_urls yes配置完用conda config --show-sources和python -m pip config debug复核一遍。这里有个我踩过的坑给 conda 加了太多 channel 之后conda 求解依赖会变得极慢有时候要等好几分钟。如果不是特别需要channel 数量控制在两到三个就够。另外提醒一句conda 换源和 pip 换源是两个独立的操作只换一个另一个还是慢。我以前就犯过只换了 pip 源的错误然后conda install卡在那儿半天不动还以为是网络问题。5.5 一份可以抄走的验证脚本把上面的检查串成一条脚本每次建完环境跑一遍心里踏实#!/usr/bin/env bash echo 解释器路径 python -c import sys; print(sys.executable) echo sys.path python -c import sys; [print(p) for p in sys.path] echo 环境变量 echo PYTHONPATH${PYTHONPATH:-empty} echo PYTHONHOME${PYTHONHOME:-empty} echo site 配置 python -m site echo 已安装包 python -m pip list如果输出里 sys.path 干净、环境变量为空、pip list 只有个位数行那这个环境就是真的干净了可以放心往里装东西。6. 报错速查那些和“包多”同源的毛病6.1 现象、根因与处理对照表排查得多了会发现很多看起来不相关的报错根子都在同一处。我把常见的整理成一张表方便对着查现象可能的根因处理方式新环境 pip list 几十个包create_default_packages 非空conda config --remove-key create_default_packages删环境重装仍然有多余包USER_SITE 被挂载设PYTHONNOUSERSITE1或pip config set global.user false只有某个环境的包异常多PYTHONPATH 指向别处清空 PYTHONPATH 后重开终端所有环境都能 import 某个包曾经pip install --user清理 user-site 目录pip命令报找不到环境未激活或 PATH 未刷新用python -m pip代替或重开终端激活环境报 conda init 相关提示shell 未初始化执行conda init bash后重开终端pip list和conda list结果差很多两套包管理系统各自记录以conda list为主pip 装的包用它兜底pip 报 launcher 相关错误pip 可执行文件与解释器路径不匹配用python -m ensurepip --upgrade修复这张表里最后两条值得展开说一下。pip list和conda list结果不一致是正常现象因为 conda 装的包会进 conda 的包数据库pip 装的包只在 site-packages 里有元数据。**判断一个环境里到底有什么最保险的做法是两条命令都跑取并集。**我自己写环境清理脚本的时候就是这么干的。至于 pip 可执行文件路径不匹配那个报错典型表现是提示无法创建进程、指向了一个已经不存在的 Python 路径。原因通常是删过 Python 安装目录但 pip 的启动器脚本还留在 PATH 里。修复方法是先找到当前 python 的路径然后用python -m ensurepip --upgrade重建 pip再检查 PATH 里有没有指向旧路径的条目。6.2 pip 指错环境这类问题“pip 装到别的环境去了”是仅次于“包多”的高频问题而且更隐蔽因为装的时候不报错只是你在这个环境里 import 的时候找不到。判断方法还是那两行which pip pip -Vpip -V输出里的路径必须和which python输出里的环境路径一致。不一致的话说明你的 PATH 里 pip 的位置排在 conda 环境前面。处理方式有两种。省事的做法是全程用python -m pip install绕开 PATH 的干扰。根治的做法是检查 PATH 顺序把 conda 环境的路径提到前面。conda activate 本身就会做这件事所以如果 PATH 乱了通常意味着你手动改过 PATH或者有别的工具在激活时插队。Windows 上还有一个特殊情况如果你装了多个 Python 发行版Microsoft Store 版的 Python 会在%LOCALAPPDATA%\Microsoft\WindowsApps放一个 python.exe 的占位符这个路径经常排在 PATH 最前面。表现就是敲 python 打开的是商店或者 pip 指向了一个奇怪的路径。处理办法是在“应用执行别名”设置里把这两个别名关掉。6.3 PyCharm 配 conda 解释器时的注意点用 PyCharm 的同学还有一个额外的坑IDE 里配置的 conda 解释器路径不对会导致 IDE 里看到的包和终端里看到的完全不一样。正确的配置方式是在解释器设置里选择“添加解释器 → Conda 环境 → 使用现有环境”然后选择的python 可执行文件路径应该是Windowsconda安装目录\envs\环境名\python.exeLinux/macOSconda安装目录/envs/环境名/bin/python注意不要选到 conda 安装目录下的condabin或者 base 目录那是两个最常见的误选点。另外如果 PyCharm 版本较新它也支持直接读取 conda 的环境列表这种情况下选环境名就行。配置完之后在 PyCharm 的终端里跑一次which python和pip -V交叉验证。IDE 集成的终端有时候环境变量和系统终端不完全一致这一步能提前发现差异。6.4 内网、无网络机器上的替代做法有些同学的工作机在内网装不了包只能靠离线的方式搭环境。这种场景下 conda 的优势比较明显因为它支持--offline和本地 channel。基本流程是# 在有网机器上打包 conda pack -n cleanenv -o cleanenv.tar.gz # 拷到目标机器解压到指定环境的目录 mkdir -p ~/miniconda3/envs/cleanenv tar -xzf cleanenv.tar.gz -C ~/miniconda3/envs/cleanenv # 激活前先 conda init之后 conda activate cleanenv 即可conda pack会把环境和依赖一起打成一个压缩包解压出来就是可用的。注意打包机器和目标机器的操作系统、架构要一致Linux 打的包不能拿到 Windows 上用。如果只是要装几个纯 Python 的包还有个更轻的路子在有网机器上用pip download把 whl 文件下下来拷进去之后用pip install --no-index --find-links./wheels xxx离线安装。这个方式要求目标机器的 Python 版本和 whl 的兼容标签匹配。顺便说近两年uv这个工具在离线场景也挺好用uv venv建出来的环境默认就只有 pip 和 setuptools干净程度比 conda 还高而且创建速度极快。不过它和 conda 是两套体系不建议在同一个环境里混着用。7. 我个人的几条习惯排查这类问题这么多年我攒了几个小习惯写出来供参考。第一任何环境操作前先确认三件事which python、which pip、python -m site。这三条命令加起来不到十秒但能挡掉绝大多数“为什么装到别处去了”的问题。我现在基本是肌肉记忆了切换环境之后手会自动敲一遍。第二新环境建好立刻记录基线。用python -m pip list --formatfreeze baseline.txt存一份以后再出现包异常增多直接 diff 一下就知道多出来的是什么不用凭记忆猜。这个习惯在做环境迁移和交付的时候尤其有用。第三尽量不在 base 环境里装业务包。base 环境一旦被污染所有派生环境都可能受影响而且 base 出问题修起来最麻烦。我现在 base 里只有 conda 自己和几个基础工具项目一律建独立环境。第四pip 和 conda 不要混用得太随意。如果一个包 conda 能装优先用 conda只有 conda 没有的时候才用 pip。混用的代价是 conda 后续求解依赖时可能检测不到 pip 装的包的约束关系导致升级时把环境搞坏。真要混用建议先 pip 后 conda 的顺序反过来也就是 conda 先装完再用 pip 补缺。最后再分享一个小技巧如果你经常要在多台机器上复现同一套环境用conda env export --no-builds environment.yml导出配置可以去掉平台相关的构建号跨平台复现的成功率会高不少。导出之后手工检查一遍把 pip 段里通过 URL 安装的包换成版本号这样在没网的机器上也能装。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →