一、我的问题

关于 conda 和 pip 协同使用的问题。我曾咨询过 DeepSeek,他的建议如下:

协同使用的最佳实践:如果确实需要在 conda 环境中安装 conda 仓库没有的包,可以按以下顺序操作:

– 优先用 conda 安装:尽可能通过 conda install 满足依赖。

– 最后用 pip 补充:仅对 conda 找不到的纯 Python 包使用 pip install。务必避免在 conda 的 base 环境中使用 pip,并在导出环境时确保 environment.yml 同时捕获 conda 和 pip 安装的包。

总结:pip 是 Python 世界的“标准快递员”,轻便且无所不达;conda 则是“全能管家”,能打理好整个项目环境,尤其是涉及复杂底层依赖时。理解它们的分工,就能在合适的场景选用合适的工具。

我在网上寻求“conda 和 pip 如何协同使用”的答案的时候,看到另外一个建议:

只用 conda 管理虚拟环境,用 pip 安装、管理所有的包(除非没有)。

我的近期真实案例似乎验证了第二种建议。

1. 下载安装 miniconda3,创建虚拟环境 ibase 并激活

conda create -n ibase python=3.14.7
conda activate ibase

2. 然后 conda 安装了 ipykernel、numpy、matplotlib

conda install ipykernel numpy matplotlib

3. 因为 conda 没有 pyside6,只能 pip 安装

pip install pyside6

4. 然后运行 pyside6 程序,出现著名的“ImportError: DLL load failed while importing QtWidgets”报错。

我逐步用排除法发现,是 conda matplotlib 的时候,自动附带安装了几个 Qt 包,与 PySide6 冲突了。

于是我在 conda 创建、激活虚拟环境后,全部用 pip 安装包:

pip install ipykernel numpy matplotlib pyside6

一切就都正常了。并且后续又 pip 安装了项目很多的依赖包,包括 pytorch,也都工作正常。

那么,回到最初的问题:conda 和 pip 协同使用的最佳实践,到底是什么?

二、DeepSeek Harness的回答

一句话结论

“conda 管环境、pip 装一切”不是妥协,而是一种正经的最佳实践——但它和“conda 优先”是同一个原则的两种实现,那个原则是:一个环境里,同一份原生依赖只能有一个提供者。 你的案例失败的真正原因不是“pip 不好”,也不是“顺序不对”,而是同一个原生库被两个包管理器各装了一份。

先把你这个案例的归因做精确

你的判断方向对,但原因比“顺带装了几个 Qt 包”更具体。Conda-forge 的 `matplotlib` 是个元包,它的运行依赖里明写着:

- name: matplotlib
run:
- python
- pyside6 >=6.7.2   # [cpython 且非 free-threading、非 osx/ppc64le]
- tornado >=5
- matplotlib-base

(出自 [matplotlib-feedstock 的 meta.yaml](https://github.com/conda-forge/matplotlib-feedstock/blob/main/recipe/meta.yaml))

也就是说:

1. `conda install matplotlib` 拉进来的是 conda 构建的 `pyside6` + 它依赖的 `qt6-main`,Qt 的 DLL 落在 `%CONDA_PREFIX%\Library\bin`,激活时被前置进 `PATH`。

2. 你随后 `pip install pyside6`,PySide6 的 wheel 是自包含的:它把 Qt6 的整套 DLL 塞在 `site-packages\PySide6\` 里。

3. 于是同一个进程里有两套同名不同构建的 Qt6 DLL。Windows 加载器按 PATH / `os.add_dll_directory` 的顺序取第一个命中的,`shiboken6` 与 `QtWidgets` 版本一旦错位,就是那句 `ImportError: DLL load failed while importing QtWidgets`。

这个报错在 Windows 上几乎永远是“同名 DLL 被错误的那一份抢先加载”,而不是“DLL 缺失”。你后来全 pip 之所以好,是因为 conda 那套 Qt 根本不存在了——环境里只剩一套自包含的二进制栈。

机制层面:conda 和 pip 的二进制模型天生不同

condapip
原生库位置集中在 `$PREFIX/Library/bin`(Win),全环境共享一套每个 wheel 自带(`site-packages/PySide6`、`torch/lib`)
ABI保证方式所有包在同一 solver 里 lockstep 对齐(共享即保证)各 wheel 自包含,互不知情
冲突风险与外部/另一套共享库抢加载与 PATH 上另一套同名库抢加载

两者的优点正好是彼此的雷区:conda 的“全局唯一一套”遇上 pip 的“每包自带一套”,就是双重提供。

所以那两条建议各自在什么条件下成立

建议一(conda 优先,pip 只补纯 Python 包) 成立于:项目需要 conda 的独特价值——非 Python 依赖(R、MPI、编译器、GDAL/PROJ/GEOS 工具链、ffmpeg)、需要 conda-lock 级别的严格复现、或整个科学栈的二进制一致性。这时 conda 必须是**唯一的原生库提供者**,pip 只能进纯 Python 包。

建议二(conda 只管环境,包全走 pip) 成立于:项目由“自带原生库的 wheel”组成——PySide6/PyQt、PyTorch、OpenCV、onnxruntime、tensorflow。这些项目的上游文档本身就以 pip wheel 为准,wheel 里已经把 Qt/CUDA/OpenSSL 打好了。这时让 pip 独占二进制栈,反而是**更少惊喜**的选择。

你的项目正好落在第二种里:Qt GUI + PyTorch + 数据科学,全是 wheel 齐备的组件。

硬规则(如果非要混装)

1. 顺序:conda 先、pip 后。先立二进制骨架,再用 pip 补纯 Python。反过来 conda 会覆盖 pip 写下的文件。

2. 绝不 `pip install` 一个 conda 已经装了的包。要换来源,先 `conda remove` 再 pip,或反过来。检查用 `conda list`:channel 列显示 `<pip>` 的才是 pip 装的。

3. 绝不 `pip install –upgrade` 一个 conda 管的包。这会静默换掉 conda 的 ABI 契约。

4. 同一个 GUI 工具链只留一套。要 pip 的 PySide6,就先 `conda remove pyside6 qt6-main`;要 conda 的,就别 pip。

5. conda 装 matplotlib 请用 `matplotlib-base`,不要用 `matplotlib`——后者会顺带 pyside6(`defaults` 频道则是 pyqt,同理中招)。

6. 混装后导出环境,必须用完整的 `conda env export > environment.yml`,它才会多出 `pip:` 段落;`–from-history` 不包含 pip 装的包,会漏。

7. `conda-forge` 和 `defaults` 不要混着用,同一包的依赖声明不一样(第 5 条就是活例)。

决策树

– 项目里有 PySide6/PyQt、PyTorch、OpenCV、tensorflow、onnxruntime 任何一个 → 走 pip 独占:`conda create -n x python=3.14 pip`,其余全 `python -m pip install`。

– 项目里有 GDAL/GEOS/PROJ、R、MPI、CUDA toolkit、ffmpeg、graphviz、编译器 → 走 conda 独占:`conda install` 到底,pip 只在缺纯 Python 包时用,且用 `–no-deps` 前先确认不碰共享库。

– 同一个环境不要同时采用两种形态,这是唯一真正的“错误答案”。

一个可直接抄的配方:

conda create -n proj python=3.14 pip -y
conda activate proj
python -m pip install --upgrade pip
python -m pip install pyside6 matplotlib numpy ipykernel
python -m pip check          # 至少过一遍一致性

用 `python -m pip` 而不是裸 `pip`,能保证调用的是当前解释器自己的 pip,而不是 PATH 上别处的。

补充两点

– 纯 pip 路线下更该考虑 `uv`:同一套“env + pip 装一切”的做法,`uv` 的解析和安装快一个数量级,还自带 `uv.lock` 锁文件——这正好补上 pip 在复现性上的短板。conda 只用来建 env 时,`uv venv` 甚至能替掉 conda。

– 你的 `python=3.14.7` 放大了这个问题:版本越新,“某个包只有 pip wheel、另一个只有 conda 构建”的概率越高,被迫混装的次数也越多。这就是为什么在 3.14 这种前沿版本上,全 pip 反而更稳。

回到你最初的问题:“最佳实践”不是一个固定配方,而是“每个环境选定唯一的二进制所有者,然后在这一前提下按包的供应情况选工具”。 你的案例不是推翻第一条建议,而是展示了它的边界——conda 优先只在你需要 conda 的二进制栈时才成立;不需要时,让 pip 独占,本身就是最佳实践。

发表评论

Trending