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

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

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

速读#

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

共享环境是烂泥潭#

Python 全局环境有个天然缺陷:所有项目共用同一个包目录,而同一个目录里,一个包只能装一个版本。项目 A 把 lib 升到 2.0,项目 B 依赖 1.x 就跟着坏——不是可能坏,是结构上注定坏,因为 1.x 已经被物理覆盖了。更阴的是时间差:坏的那个项目当时不报错,过几天重跑才暴雷,而那时早就忘了中间装过什么。

根因不是「你装错了」,是环境没隔离。venv 给每个项目一份独立包目录,从结构上消灭「互相顶版本」。

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

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

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

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

每项目一个 venv#

Python 3 自带,不用额外装:

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

激活后 pip install 只进这个目录,不污染全局。包括后来写 Django、Flask,这条习惯没变过。

venv 本身没有魔法:.venv 就是一个带独立 site-packages 的目录,「激活」也只是把它的可执行路径挂到 PATH 前面。所以有没有生效不用猜,which python 看指向就知道;脚本和定时任务里甚至不必激活,直接用 .venv/bin/python 跑,效果一样。拆穿到这一层,就不会在「激活了没有」上疑神疑鬼。

依赖清单边界#

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

小工具规模下,pip freeze 够用。但要知道它在干什么:把当前环境里所有包连同间接依赖一起钉死版本。好处是别人装出来和我一模一样;代价是清单里分不清哪些是我真正要的、哪些只是被捎带进来的。

它也比不上 Maven、Poetry 那类带锁文件的依赖管理精确。还有个容易忽略的洞:清单钉死了包版本,却不记录 Python 解释器本身的版本——同一份 requirements.txt,3.8 和 3.12 装出来的行为未必一致。承认边界,不假装万能。 多人协作的大项目,这套机制往往不够。

两条配套习惯:

  • .venv/.gitignore——环境不进库,进库的是清单
  • requirements.txt 和代码一起提交——环境随时可以删掉重建,清单才是事实源

环境问题占 bug 一大半#

一条规律贯穿后面所有事:环境问题占了我所有 bug 的一大半。

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

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

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

容器化是把 venv 的思路放大到整个运行环境:venv 隔离的是包目录,容器隔离的是从系统库到运行时的一整层——逻辑是通的。

版权许可

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

相关文章

s1oopX

登录 s1oopX