Python 虚拟环境:venv 与 poetry 的取舍

举报
yd_282015185 发表于 2026/10/26 07:16:24 2026/10/26
【摘要】 虚拟环境本身不复杂,但 PATH、sys.prefix、解释器查找顺序、IDE 配置四件事任一处没对齐,就会出现「激活了却没生效」。本文拆解四个高频坑,对比 venv 与 poetry 各自解决什么问题、代价是什么,给出按协作规模选型的判断标准,附生产可用的混合做法与两分钟自检清单。

Python 虚拟环境:venv 与 poetry 的取舍

“我明明激活了虚拟环境,pip 装完还是装到全局去了”——这句话我在不同团队里听过至少二十次。虚拟环境本身不复杂,但它牵扯到 PATH、sys.prefix、解释器查找顺序、IDE 配置四件事,任何一处没对齐,就会出现"看起来激活了、实际没生效"的假象。

这篇不站队 venv 或 poetry,只讲清楚两者各自解决了什么问题、代价是什么,以及在什么规模下该切。


一、venv 到底做了什么

很多人以为 venv 是"隔离环境",这个说法容易让人以为有沙箱或命名空间。实际上它只做三件事:

  1. 建一个目录,里面放一个指向基础解释器的 python(Windows 是拷贝或 shim)
  2. 写一份 pyvenv.cfg,记录基础解释器的位置
  3. 生成 activate 脚本,作用是把该目录的 bin(Windows 是 Scripts)插到 PATH 最前面
$ python -m venv .venv
$ cat .venv/pyvenv.cfg
home = /usr/bin
include-system-site-packages = false
version = 3.11.9

隔离的全部机制就是这个 home 字段:解释器启动时读 pyvenv.cfg,据此把 sys.prefix 从 /usr 改成 .venv,于是 site-packages 的搜索路径也跟着变。include-system-site-packages = false 则关掉了用户级 site-packages,所以 venv 里看不到 ~/.local/lib 下那堆东西。

没有任何沙箱。 所以 venv 里的进程照样能读全局文件、能装编译依赖、能污染 ~/。理解这一点,后面的坑就好解释了。


二、四个"激活了但没生效"的坑

坑 1:pip install 装到了全局

先看现象:

$ source .venv/bin/activate
(.venv) $ pip install requests
Requirement already satisfied: requests in /usr/lib/python3/dist-packages

pip 说"已满足",路径却是 /usr/lib。这说明跑的不是 venv 里的 pip。

排查只有一步——确认实际执行的是谁:

(.venv) $ which -a pip python
/home/me/proj/.venv/bin/pip
/usr/bin/pip
/home/me/proj/.venv/bin/python
/usr/bin/python

which -a 会把 PATH 上所有同名命令都列出来,第一行才是真正会执行的。如果第一行不是 .venv/bin 下的,说明 activate 没生效(常见于在子 shell 里 source、或用 sh 而不是 bash 执行脚本)。

坑 2:pip 和 python -m pip 不是一回事

即使 PATH 对了,pip 这个可执行文件也可能属于另一个解释器。系统里同时装了 pyenv、conda、apt 的 python 时特别容易乱。

可靠写法是永远用模块方式调用:

$ python -m pip install requests

python -m pip 的含义是"用当前这个 python 去加载 pip 模块",pip 和解释器在构造上就是同一个,不可能错配。这一条建议写进团队规范,成本极低,收益极高。

顺手加一道保险,让 pip 在虚拟环境外直接拒绝工作:

$ export PIP_REQUIRE_VIRTUALENV=1
$ pip install requests
ERROR: Could not find an activated virtual environment

这条可以写进 ~/.bashrc,能挡住大部分手滑。

坑 3:IDE 没跟着换解释器

命令行里一切正常,一开 IDE 补全全红、运行报 ModuleNotFoundError——IDE 有自己独立的解释器设置,跟终端里的 activate 毫无关系。

VS Code 用 Ctrl+Shift+P → Python: Select Interpreter,选到 .venv/bin/python;PyCharm 在 Settings → Project → Python Interpreter。选完记得看一眼工作区设置 .vscode/settings.json 里是否有:

{
  "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python"
}

提交这个文件能让整个团队少踩一次。

坑 4:把 .venv 提交进了 Git

.venv 里有绝对路径(pyvenv.cfg 的 home、activate 里的 VIRTUAL_ENV),换台机器或换个目录就直接坏掉,而且体积巨大。

$ echo ".venv/" >> .gitignore

如果已经提交过,光加 .gitignore 不够,历史里还在。需要 git rm -r --cached .venv 再提交一次。


三、poetry 多解决了什么

venv 只负责"隔离运行时",不负责"管理依赖"。依赖管理的痛点在于传递依赖:你写了 requests==2.31.0,它下面还挂着 urllib3、certifi、idna、charset-normalizer,pip install 装的时候版本由 pip 当场解析,今天装和三个月后装可能得到不同结果。

poetry 相对 venv 多的三件事:

  1. 有约束求解器:pyproject.toml 里写的是版本区间,poetry 求解出一组互相兼容的具体版本
  2. 有锁文件:poetry.lock 记录每个包的确切版本 + 哈希,保证换机器装出完全一致的依赖树
  3. 区分直接依赖与传递依赖:主依赖写在 [tool.poetry.dependencies],锁文件里两者都记

代价也很实在:多一层工具、多一份配置、多一个学习成本,以及导出标准依赖清单需要额外一步。poetry 1.x 内置 poetry export,2.x 把它拆成了插件,得先装:

$ poetry self add poetry-plugin-export
$ poetry export -f requirements.txt --output requirements.txt --without-hashes

四、怎么选:看协作规模

判断标准其实只有一条——是不是多人协作、是否需要可复现的构建。

场景 建议 理由
一次性脚本、临时验证 venv 不需要锁文件,pip install 够用
单人小工具 venv + requirements.txt(全部固定版本) 成本最低,够用
团队项目 poetry 锁文件能避免"我这能跑"
要发布到 PyPI poetry build + publish 一条链
需要打进 Docker 镜像 见下节 分层与锁文件的关系要单独处理

有一件事要强调:requirements.txt 不等于锁文件。手写的 requirements.txt 通常只固定直接依赖,传递依赖仍是浮动的;pip freeze 导出的版本虽然把传递依赖也固定了,但没有哈希校验,也没有"为什么锁在这个版本"的记录。如果团队只有 requirements.txt,最低要求是必须用 pip freeze 生成、必须提交、必须固定所有版本。


五、我实际在用的混合做法

生产项目上我一般这么配,兼顾两边的优点:

1) 让 poetry 把虚拟环境建在项目目录里

$ poetry config virtualenvs.in-project true

默认情况下 poetry 把 venv 建在 ~/.cache/pypoetry/virtualenvs/ 下,名字是一串哈希,找起来痛苦。改成 true 后会在项目里建 .venv,IDE 能自动发现,rm -rf .venv 就是干净重置。

2) 生产安装只认锁文件,不装开发依赖

$ poetry install --only main --no-root

--only main 跳过 dev 组,--no-root 不把项目自身装成包(镜像里不需要)。

3) Docker 里用两段式安装,避免每次改代码都重装依赖

FROM python:3.11-slim
WORKDIR /app

RUN pip install --no-cache-dir poetry==1.8.3
COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.create false \
 && poetry install --only main --no-root

COPY . .
CMD ["python", "-m", "app"]

关键在 virtualenvs.create false——容器本身就是隔离环境,再套一层 venv 纯属浪费。以及 COPY 顺序:先只拷两个依赖描述文件、装完再拷代码,这样改业务代码时这一层镜像能命中缓存。


六、一张自检清单

接手一个新项目时,我会按顺序跑这几条,基本能在两分钟内判断环境是否健康:

$ which -a python pip          # 第一行是否指向 .venv
$ python -c "import sys; print(sys.prefix)"   # 是否等于项目内 .venv 的绝对路径
$ python -m pip list --outdated | head       # 能否正常读到当前环境的包
$ git ls-files | grep -E "^\.venv/"          # 应无输出

四条全过,环境基本没问题。

至于工具本身,venv + 固定版本的 requirements.txt 能覆盖八成场景,剩下的两成交给 poetry。真正决定项目能不能复现的,不是选了哪个工具,而是锁文件有没有提交、装依赖时有没有绕过它。 换工具能解决的问题是有限的,纪律问题换不掉。

顺带一提,如果你已经觉得 poetry 太重,可以看看 uv——同样是锁文件模型,速度是 poetry 的几十倍,接口也兼容 requirements.txt。但要换工具,最好等手上项目结项时再动,别在交付期折腾环境。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。