项目 A 要 Django 4,项目 B 要 Django 5,一起装在全局环境里,装完 A 再装 B,结果 A 跑不起来了——这种"依赖打架"几乎每个 Python 开发者都经历过。虚拟环境就是给每个项目圈一块独立地盘,各装各的包,互不干扰。本文带你从创建虚拟环境开始,一直走到 pip-tools 的工程化依赖管理。
1. 依赖冲突:为什么需要虚拟环境
pip 默认把包装进全局的 site-packages,所有项目共享。一旦两个项目对同一个库的版本要求不同,后装的就会把先装的顶掉。虚拟环境的原理很简单:复制一份独立的 Python 解释器和独立的 site-packages 目录,让每个项目在自己的环境里装包。先看看你现在用的到底是哪个 Python:
import sys
print("解释器路径:", sys.executable)
print("环境根目录:", sys.prefix)
运行后记下这两行输出。等会儿激活虚拟环境后再跑一次,你会发现 sys.executable 指向了虚拟环境里的 Python,而 sys.prefix 也变了——这就是"隔离"的实质:同一段代码,在不同环境里看到的是不同的世界。
有人会问:那我不装虚拟环境,每次手动 pip install 包名==版本 不就行了?短期可以,但升级依赖、换电脑、多人协作时,手工管理版本必然出错。更糟的是全局环境被搞乱后很难恢复——你不知道哪个项目依赖哪个包,删也不敢删。虚拟环境把"全局混乱"变成"每个项目自治",代价只是磁盘上多几百 MB,这笔账怎么算都划算。
2. 创建你的第一个虚拟环境
从 Python 3.3 开始,标准库自带 venv 模块,不需要装任何第三方工具:
# 创建名为 myenv 的虚拟环境(在项目目录下执行)
python3 -m venv myenv
# Linux / macOS 激活
source myenv/bin/activate
# Windows(CMD 或 PowerShell)
myenv\Scripts\activate
# 用完退出环境
deactivate
激活后,终端提示符前面会出现 (myenv) 字样,此时输入的 python 和 pip 都指向虚拟环境里的版本。一个常见误解是"激活"很神秘,其实它只是修改了 PATH 环境变量,让系统优先找到虚拟环境里的可执行文件。不激活也能用,直接写 myenv/bin/python -m pip install xxx 效果一样,只是麻烦些。
好奇的话,可以看看虚拟环境目录里有什么:Linux/macOS 下 bin/ 放着 python、pip 的可执行文件(其实是指向系统 Python 的软链接),lib/ 下的 site-packages 是将来装包的地方,pyvenv.cfg 记录了它基于哪个 Python 创建。理解了这个结构,你就明白为什么虚拟环境"轻"——它没有复制解释器,只是建了一套独立的目录和链接。另外建议在项目根目录创建环境并固定命名(如 .venv),团队约定俗成,谁都不用猜。
3. 用 pip 管理包
激活环境后,安装、查看、卸载包都围绕 pip 进行:
# 安装包
pip install requests
# 查看已安装的包
pip list
# 卸载包
pip uninstall requests
# 导出当前环境的完整依赖清单
pip freeze
pip freeze 是关键命令,它会输出所有已装包及精确版本号,格式是 包名==版本号。注意 pip list 和 pip freeze 的区别:前者给人看,后者给机器用——freeze 的输出可以直接存成文件,让别的地方一键复现你的环境。
装包时还有几个常用技巧:想升级某个包用 pip install --upgrade 包名;想装指定版本用 pip install 包名==1.2.3;想确认某个包是否已装、装的什么版本,用 pip show 包名。另外 pip 本身也值得定期升级(pip install --upgrade pip),新版 pip 的依赖解析更可靠,遇到"装不上"的玄学问题,先升级 pip 再试往往就好了。多数包现在都发布 wheel 格式的预编译包,装起来秒完成;如果看到源码编译的日志刷屏,先检查是不是平台/版本不匹配。
4. requirements.txt:让环境可复现
把 pip freeze 的结果存成 requirements.txt,项目就有了"环境配方"。换电脑、换同事、上服务器,一行命令重建:
# 在虚拟环境里生成配方文件
pip freeze > requirements.txt
# 新机器上重建环境
python3 -m venv newenv
source newenv/bin/activate
pip install -r requirements.txt
requirements.txt 内容长这样:
requests==2.31.0
click==8.1.7
Flask==3.0.0
== 把版本钉死,保证任何机器装出来的环境完全一致,这是"可复现"的关键。如果想要一定范围的灵活性,可以用 requests>=2.30 或 <3.0,但生产环境强烈建议锁死版本。装完包记得随时重新生成 requirements.txt,别让它过期。
项目规模上来后,单一 requirements.txt 不够用了:开发环境要装 pytest、调试工具,生产环境不需要。常见做法是拆成 requirements.txt(生产依赖)和 requirements-dev.txt(开发依赖,里面 -r requirements.txt 再追加开发工具),或者用 requirements/base.txt、requirements/dev.txt 的目录结构。原则只有一个:生产环境装的东西越少越好——少一个包,就少一个攻击面和升级风险。
5. 依赖管理的工程实践:pip-tools
大型项目有个痛点:pip freeze 会把所有间接依赖(依赖的依赖)也列出来,文件又臭又长,升级一个包牵一发动全身。pip-tools 的解决思路是两层清单:你只维护顶层依赖,它负责解析出完整的锁定版本:
pip install pip-tools
# 手动写好 requirements.in,里面只列直接依赖
pip-compile requirements.in # 生成 requirements.txt(含全部间接依赖)
pip-sync # 让当前环境与 requirements.txt 完全一致
对应的 requirements.in 只需要三行:
requests
flask
pytest
pip-compile 会分析出每个包依赖的传递链,自动选定兼容的版本并生成锁定的 requirements.txt;想升级某个包,改 .in 文件重新 compile 即可。这套工作流和 npm 的 package.json + package-lock.json 是同一个思路,团队协作时依赖变更一目了然。
值得强调的是 pip-sync 的"对齐"能力:它会比对当前环境和 requirements.txt,多装的自动卸载,少装的自动补上。这意味着你可以在环境里放心地乱装包做实验,最后一条 pip-sync 让环境回到干净状态。多人协作时,requirements.txt 的 diff 就是依赖变更的完整历史,Code Review 时一眼能看出谁加了什么。
6. 常见坑与排查技巧
虚拟环境用多了,总会遇到几个经典翻车现场。最常见的三个:忘了激活就在全局装包、激活了但 pip 指向的还是全局、虚拟环境被误提交进 Git 仓库。逐个排查:
# 当前 python 到底是哪个?是不是虚拟环境里的?
which python
# 当前环境的 site-packages 在哪?
python -c "import site; print(site.getsitepackages())"
# 确认 pip 装到了哪
pip --version
另外,虚拟环境目录千万别提交进 Git——它包含本机路径和成百上千个文件,换台机器就没用了。.gitignore 里加一行 venv/ 或 myenv/,让仓库只保留 requirements.txt 这个"配方",任何人 clone 下来重建即可。还可以用 python -m pip 代替裸 pip,避免系统里多个 Python 版本时调错对应的 pip。
最后补一个多版本共存的场景:机器上同时有 Python 3.9 和 3.11,想分别验证项目兼容性怎么办?python3.9 -m venv venv39 和 python3.11 -m venv venv311 各建一个环境,互不干扰。给环境起名时带上版本号(如 .venv39),日后一眼知道它基于哪个解释器。记住口诀:先用 which python 确认当前解释器,再动手装包,这条能避开 90% 的"装错地方"事故。万一装错了也别慌,在正确的环境里重新 pip install 一遍即可,全局环境里多出的包可以留着,不影响项目。
7. 总结与练习
核心要点就三句话:虚拟环境用 python3 -m venv 创建、激活后各项目独立装包;pip freeze > requirements.txt 导出配方、pip install -r 一键复现;团队项目用 pip-tools 维护"顶层依赖 + 锁定版本"两层清单。
- 练习 1:给一个旧项目创建虚拟环境,把依赖导成
requirements.txt,再删掉环境从零重建,验证能一键还原。 - 练习 2:在两个不同虚拟环境里分别装
requests==2.30.0和最新版,用pip list对比,感受隔离的效果。 - 练习 3:用 pip-tools 管理一个练习项目的依赖,试试修改
requirements.in后重新 compile,观察锁定文件的变化。
💡 判断一个 Python 项目是否专业,先看它有没有requirements.txt和.gitignore里的虚拟环境条目。依赖管理是工程化的第一步,比写业务代码更早遇到,也更容易被忽视。