「在我电脑上能跑」——环境地狱的临床症状:A 项目要 Python 3.9 + torch 2.0,B 项目要 Python 3.11 + torch 2.3,全局安装互相覆盖;三个月后重装系统,requirements.txt 里的 pandas 没写版本,装上最新版 API 全变。
这篇按项目需求比较 venv、conda、uv 和 Docker:要隔离 Python 包、管理原生依赖、锁定安装结果,还是打包服务运行环境?它们有交集,也可以组合使用,不存在一个替代其他所有工具的终局方案。
前置阅读:建议先读 Linux + Python 环境基础。
虚拟环境:一个项目一个隔离世界
原理一句话:每个项目一个独立目录,里面有自己的 Python 解释器链接和 site-packages,激活后 pip install 只装进这个目录,互不污染。
# 标准流:venv + pip
python -m venv .venv
# Windows 激活
.venv\Scripts\activate
# Linux/Mac
source .venv/bin/activate
pip install numpy pandas
pip freeze > requirements.txt # 锁定当前环境
三条铁律:
.venv放项目内,一眼可见,删项目连环境一起删,无残留。.venv加进.gitignore——环境不进 Git,进 Git 的是requirements.txt。- requirements 必须锁版本:
numpy==1.26.4而不是numpy。裸包名意味着「每次安装抽盲盒」。
conda:它的主场从来不是 Python 包
conda 被误用最多的地方是当 pip 用。它真正的价值:管理非 Python 的二进制依赖——CUDA 工具链、GDAL、ffmpeg、MKL 这些编译型依赖,pip 装不了或装不明白,conda 能给你编译好的二进制。
conda create -n ml python=3.11
conda activate ml
conda install cudatoolkit=12.1 # pip 装不了的东西
pip install torch # Python 包仍然用 pip
conda 的现实问题:solver 解依赖慢(mamba 曾为此而生);channel 混杂(defaults/conda-forge 混用是冲突重灾区);商业使用注意 Anaconda 仓库的 license(conda-forge 免费)。2026 年的建议:系统级二进制依赖才考虑 conda,纯 Python 项目不必。
uv:Rust 写的速度革命
Astral 出品的 uv(做 ruff 的同一家)把 pip 的体验重写了一遍:并行下载、全局缓存、Rust 求解器——安装速度是 pip 的 10~100 倍,大项目从几分钟到几秒。
# 安装 uv 后,工作流基本平替
uv venv # 创建 .venv(比 python -m venv 快)
uv pip install numpy pandas # 快一个数量级
uv pip freeze > requirements.txt
# 项目管理新模式(对标 poetry)
uv init myproject
uv add numpy # 自动写 pyproject.toml + uv.lock
uv sync # 按 lock 文件精确还原环境
uv run train.py # 免激活直接跑
uv.lock 跨平台锁定全部依赖哈希——比 requirements.txt 更强的可复现性。团队 2026 年新建项目我默认 uv;老项目 uv pip 兼容 requirements.txt,迁移零成本。
pyproject.toml:一个文件说清一切
现代项目用 pyproject.toml 统一管理(替代 setup.py/requirements 的多头马车):
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = [
"numpy>=1.26,<2.0",
"pandas>=2.0",
]
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"] # uv add --dev pytest
依赖声明的纪律:直接依赖写兼容范围(>=1.26,<2.0),lock 文件锁精确版本——范围给人看(兼容意图),lock 给机器用(精确复现)。
Docker:环境的终极答案
虚拟环境隔离的是 Python 包,隔离不了系统库、环境变量、操作系统差异。「开发能跑生产挂」的最后一公里由 Docker 终结:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]
镜像 = 代码 + Python + 全部依赖的快照,任何机器跑出同样的结果。GPU 场景用 nvidia/cuda 基础镜像。Docker 化的详细内容在工程系列里有专篇。
决策树:我该用哪个
这些工具并不处在同一层:虚拟环境隔离包,锁文件描述依赖解析结果,容器封装用户空间运行环境。它们可以配合使用。容器也不会自动消除宿主驱动或硬件差异;选型应从项目要复现到哪一层开始。
项目要 CUDA/系统级二进制依赖?
├── 是 → conda(或系统包管理器)打底 + pip/uv 装 Python 包
└── 否 →
├── 新项目 → uv(venv + pip 的现代形态)
├── 老项目维护 → 保持 venv + pip,新装包用 uv pip 加速
└── 要交付/部署 → 最后都装进 Docker
混用警告:同一环境里 conda 和 pip 交替装包是冲突之源——conda 装过的包 pip 升级会留下两套元数据。一个环境一个管家:conda 管的包只用 conda 更新,pip 装的只用 pip。
踩坑排查
| 现象 | 原因 | 解法 |
|---|---|---|
| 装了包却 import 不到 | pip 和 python 不属于同一环境 | 用 python -m pip install 保证同源 |
| 重装后代码报错 | requirements 没锁版本,装到新版 | 锁版本;或用 lock 文件 |
| conda 解依赖转圈几分钟 | solver 慢 + channel 混杂 | 统一 conda-forge;或换 uv/pip |
| Windows 上 activate 报错 | PowerShell 执行策略限制 | Set-ExecutionPolicy RemoteSigned -Scope CurrentUser |
| 部署机器上包装不上 | 内网无 PyPI 访问 | 离线 wheel 包(pip download)或私有镜像源 |
| 两个项目 torch 版本打架 | 共用一个环境 | 每项目独立 venv,别共享 |
练习
- 用 venv 建两个环境分别装 pandas 1.x 和 2.x,验证同一脚本在两个环境下的行为差异(如
df.append的移除)。 - 把一个老项目的 requirements.txt 迁移到 uv:
uv init+uv add,对比uv pip install和pip install的耗时。 - 给你的项目写 Dockerfile,构建镜像后在容器里跑通训练脚本。
- 排查练习:故意在一个环境里 conda 装 numpy 再用 pip 升级,观察
conda list和pip list的不一致。
面试常问
Q:venv 的原理是什么?
创建目录含 Python 解释器的副本/符号链接 + 独立 site-packages,activate 脚本修改 PATH 把该目录的 bin/Scripts 置顶——之后 python/pip 命令解析到环境内的可执行文件,包装进环境的 site-packages。没有黑科技,全是 PATH 魔法。所以 venv 可随意删除,系统 Python 毫发无损。
Q:conda 和 pip 的区别? pip 是 Python 包安装器(只管 Python 生态,从 PyPI 装 wheel/sdist);conda 是跨语言包与环境管理器(管 Python + C 库 + 二进制工具,从 anaconda channel 装编译好的包)。conda 的环境管理 = venv 的超集;包管理上二者元数据互不感知——混用会互相覆盖而不知。
Q:requirements.txt 和 pyproject.toml + lock 的优劣? requirements 是扁平列表(pip freeze 产物),不含「直接依赖 vs 传递依赖」的区分、无跨平台哈希校验;pyproject 声明意图(范围),lock 锁定全量精确版本+哈希,可复现性和安全性都更强。requirements 简单通用仍是部署常用格式;新项目管理推荐 pyproject + uv.lock/poetry.lock。
Q:CUDA 环境下 torch 怎么装才不踩坑?
先确定驱动支持的 CUDA 版本(nvidia-smi 右上角),再选对应 torch wheel(pytorch.org 的选择器生成命令,cu121/cu124 后缀)。conda 装 cudatoolkit 与 pip 装 cu 后缀 wheel 是两条路,选一条走到底。torch、CUDA、驱动、nvcc 四者版本矩阵是环境问题的重灾区,装完先 torch.cuda.is_available() 验证。
Q:为什么 Docker 是环境的「终极答案」? venv/conda 隔离 Python 层,Docker 隔离整个操作系统层——系统库、glibc、环境变量、文件布局全部封装进镜像。「在我电脑上能跑」的终极解法不是更小心地管理环境,而是把环境本身变成代码(Dockerfile)纳入版本管理。代价:镜像体积、构建时间、GPU 透传的配置复杂度。
相关阅读
- Linux + Python 环境基础——环境管理的起点
- Docker 化训练环境与 CI/CD——容器化的完整展开
- MLOps:实验跟踪与监控——可复现性在 ML 中的更高要求
- 模型压缩与部署——部署环境的一致性保障
环境管理没有银弹,只有纪律:一个项目一个环境、依赖声明写范围、lock 文件进 Git、交付用 Docker。四句话背下来,环境地狱与你无关。
💬 评论
这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。