速读#
学 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 系统包:
python -m venv .venv
# Linux / macOSsource .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。虚拟环境包含绝对路径等本机信息,不应复制到另一台机器,删掉重建才是正常用法。
依赖清单边界#
python -m pip freeze > requirements.txtpython -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 架构、外部数据与网络仍不在镜像里。逻辑相通,边界不能混为一谈。
