← 全部笔记

Python 虚拟环境:为什么不能直接 pip install

2026-10-29 编程与语言 约 7 分钟

多项目并行开发时,最容易出现的状态是「A 项目要 1.2 版,B 项目要 2.0 版,装哪个另一个就崩」。 虚拟环境解决的就是这个问题,原理比想象的简单。

一、venv 建出来的到底是什么

它不是复制了一整份 Python 解释器,只是一个目录,里面放了几样东西:

  • pyvenv.cfg 记录基础解释器的路径和版本。
  • bin/python(Windows 是 Scripts\python.exe)指向系统解释器的链接或小副本。
  • lib/python3.x/site-packages/ 这个项目专属的第三方包目录,关键在这里。
  • bin/activate 一个 shell 脚本,作用就是把 PATH 和 VIRTUAL_ENV 改一下。

所以「激活」并不神奇——它没有改变 Python 本身, 只是让你敲 python / pip 时先找到这个目录里的可执行文件, 而解释器启动时会优先从同目录的 site-packages 里找包。

二、创建与激活

python3 -m venv .venv

# Linux / macOS
source .venv/bin/activate

# Windows PowerShell
.venv\Scripts\Activate.ps1

# Windows cmd
.venv\Scripts\activate.bat

# 退出
deactivate

.venv 这个目录名是约定俗成的,好处是主流编辑器和工具会默认去这里找解释器。 它不该进版本库——在 .gitignore 里加一行就够。

.venv/
__pycache__/
*.pyc

三、requirements.txt 的正确用法

pip install -r requirements.txt        # 按文件装
pip freeze > requirements.txt         # 把当前环境导出来

pip freeze 会导出所有已安装的包,包括依赖的依赖。 这在「复现一个已知能跑的环境」时很方便,但它有两个明显缺点:

  • 把间接依赖也钉死了,别人装的时候会失去灵活升级的空间。
  • 不记录哈希,也不区分「直接依赖」和「顺带装上的」。

更清楚的做法是手写只列直接依赖的 requirements.in, 再用 pip-tools 生成带完整约束的 requirements.txt; 或者用 pyproject.toml 描述依赖,由构建工具锁定。

# 一个手写的最小 requirements.txt
requests>=2.31,<3
flask~=3.0
# 注:~= 表示允许补丁位变化,即 3.0.x

四、镜像源

国内直连官方源经常超时,可以换成镜像:

# 临时用一次
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests

# 写进配置(推荐,一次配置长期有效)
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip config list          # 检查当前生效的配置

配置写在 ~/.config/pip/pip.conf(Windows 是 %APPDATA%\pip\pip.ini), 只影响当前用户,不会污染系统。

五、别用 sudo pip

sudo pip install 会把包装进系统 Python,可能覆盖发行版自己依赖的版本, 后果是包管理器(apt/dnf)跟着出问题——而且当时完全看不出来。 需要装到用户目录时用:

pip install --user requests

六、externally-managed-environment 是什么

新版 Debian / Ubuntu 上直接 pip install 会报:

error: externally-managed-environment

× This environment is externally managed

这是 PEP 668 引入的保护:系统 Python 归包管理器管,pip 不许往里装东西。 正确反应是建虚拟环境,而不是加 --break-system-packages 把它关掉。 真要装成系统级命令,就打成包(pipx 是专门干这个的)。

七、几个小习惯

  • 项目根目录放 .python-version,配合 pyenv 固定解释器版本,避免「我这能跑」。
  • 装完依赖先 pip check,它会报出依赖冲突,比如 A 要 urllib3<2 而 B 要 >=2。
  • 升级单个包用 pip install -U 包名,但别不加思索地 pip install -U 全量升级,容易一次崩一串。

一句话总结:每个项目一个 venv,装依赖用 requirements.txt 记录,别用 sudo pip;遇到 externally-managed-environment 就该建环境,而不是加 --break-system-packages。