跳到正文
为什么我坚持每个 Python 项目一个 venv
为什么我坚持每个 Python 项目一个 venv

为什么我坚持每个 Python 项目一个 venv

学 Python 第一件该养成的习惯不是写代码,是管环境。这篇不讲 venv 语法,讲我往全局装包装出事之后,为什么把「每项目一个 venv」当成新建项目的第一条命令。

速读#

学 Python 第一件该养成的习惯不是写业务,而是给每个项目一份独立环境。venv 解决的是全局依赖互相顶版本这类烂账,后面的 Docker 只是把同一条隔离线放大到整个运行时。

共享环境是烂泥潭#

多个项目共用同一个 Python 环境时,同一发行包通常只能安装一个版本。项目 A 把依赖升到 2.0,仍需要 1.x 的项目 B 就可能在下次运行时坏掉;如果两边的版本范围刚好兼容,也可能暂时没事,但共享环境把本来独立的变更绑在了一起。现代 Linux 发行版还常通过 PEP 668 把系统 Python 标记为 externally managed,直接阻止普通 pip 修改它,这正是在保护操作系统自己的工具链。

根因不是某条安装命令写错,而是项目没有独立依赖环境。venv 默认给每个项目一份独立 site-packages 和解释器入口,从结构上切断项目之间互相覆盖包版本;它不隔离操作系统库,也不会替我选择正确的 Python 版本。

两个项目打架之后才栽明白#

当时图省事,两个项目都装在全局:一个 Flask 博客、一个数据处理脚本,各自依赖同一个库的不同版本。脚本安装时把版本顶到 v2.0,博客过几天重跑,接口直接炸。

排查路径很折磨:先怀疑自己代码 → 再怀疑 Flask 版本 → 最后才发现依赖被另一个项目悄悄顶掉。从报错到定位,一个多小时;一开始用 venv,这事根本不会发生。

那次之后:新建项目第一条命令永远是 python -m venv .venv

每项目一个 venv#

venv 属于 Python 标准库,但 Debian、Ubuntu 的精简安装可能还要先装对应的 python3-venv 系统包:

Terminal window
python -m venv .venv
# Linux / macOS
source .venv/bin/activate
# Windows PowerShell
.\.venv\Scripts\Activate.ps1

激活后使用 python -m pip install ...,可以明确让当前解释器调用自己的 pip,避免 PATH 中另一个 pip 抢先。包括后来写 Django、Flask,这条习惯没变过。

venv 本身没有魔法:.venv 是带独立包目录和解释器入口的目录,激活脚本主要把它的可执行目录放到 PATH 前面,并设置提示与 VIRTUAL_ENV。Linux、macOS 用 command -v python,PowerShell 用 Get-Command python 就能核对实际路径;脚本、systemd 和定时任务甚至不必激活,直接调用 .venv/bin/python,Windows 则调用 .venv\Scripts\python.exe。虚拟环境包含绝对路径等本机信息,不应复制到另一台机器,删掉重建才是正常用法。

依赖清单边界#

Terminal window
python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt

小工具规模下,在一个刚创建、只装了项目依赖的 venv 里执行 pip freeze 往往够用。它记录当前环境里所有可见包,包括间接依赖,能显著缩小版本漂移;代价是清单分不清哪些是项目直接需要、哪些只是传递依赖,也可能混入本地路径、editable 安装或只适用于当前平台的包。

固定版本仍不保证“另一台机器字节级一样”:Python 版本、操作系统、CPU 架构、可用 wheel、包索引内容和系统动态库都会影响结果,普通 freeze 也不带每个下载文件的 hash。Maven POM 同样不是天然锁文件,不能拿语言生态做简单高下比较。多人协作的应用更适合在 pyproject.toml 声明顶层依赖与 requires-python,再用 uv、Poetry、PDM 或 pip-tools 之一生成并检查锁定结果;具体选一个团队已经在用的,不为“现代化”叠四套工具。

两条配套习惯:

  • .venv/.gitignore——本机环境不进库
  • 代码同时提交依赖声明、锁定结果和支持的 Python 版本——环境应能删掉重建

我早期很多 Bug 都来自环境#

在我早期几个项目里,依赖、解释器和配置差异造成的问题多到足以改变习惯,但这不是可泛化的统计结论。遇到“昨天能跑、今天不能”或“我这里能跑、你那里不能”,先记录解释器路径与版本、安装来源和依赖差异,比直接重装一遍更容易留下证据。

手段治什么
venv依赖打架
Docker我机器能跑、你机器不行

守护环境,和守护进程是同一类问题:不主动隔离 / 收敛,迟早给你颜色看。

任何会在多台机器、多个项目间流动的东西,第一件事都是隔离。venv 是这条线最朴素的一站。

容器化延续了“显式声明并隔离依赖”的思路:venv 主要隔离 Python 包,容器再覆盖用户空间系统库、运行时和进程边界;宿主内核、CPU 架构、外部数据与网络仍不在镜像里。逻辑相通,边界不能混为一谈。

版权许可

CC BY-NC-SA 4.0 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

相关文章

s1oopX

登录 s1oopX