bigywater.tech
Python · 33

虚拟环境与依赖隔离:venv 背后做了什么

真实创建一个venv,对比它跟系统Python的pyvenv.cfg、sys.prefix和site-packages目录,验证依赖隔离机制到底是怎么生效的。

sys.prefix != sys.base_prefix
真实是判断是否在venv里的依据
新venv的site-packages真实是空的
不是系统包的复制品
venv装的包系统Python真实看不到
ModuleNotFoundError验证隔离

做的事:真实创建一个venv,对比它跟系统Python的差异

代码python3 -m venv venv-test cat venv-test/pyvenv.cfg python3 -c "print(sys.prefix)" # 系统解释器 ./venv-test/bin/python3 -c "print(sys.prefix, sys.base_prefix)" # venv解释器

验证:pyvenv.cfg真实记录了venv"从哪个解释器长出来"

实测 · 本机执行输出
home = /opt/homebrew/opt/python@3.14/bin
include-system-site-packages = false
version = 3.14.7
executable = /opt/.../Python.framework/Versions/3.14/bin/python3.14
command = /opt/homebrew/opt/python@3.14/bin/python3.14 -m venv venv-test

这份pyvenv.cfg真实是venv机制的核心配置文件——它没有复制一份完整的Python解释器,home字段真实指向的是创建这个venv时用的系统Python所在目录;include-system-site-packages = false真实决定了这个venv能不能看到系统全局装的第三方包(默认看不到,这也是"隔离"的关键开关之一)。venv目录里的bin/python3真实只是一个指向系统解释器的软链接或者极小的转发可执行文件,不是一份完整的Python拷贝——venv的"轻"就体现在这里,创建速度快、占用空间小,靠的是这份配置文件加运行时对sys.prefix的改写,而不是真的把整个Python安装目录复制一遍。

验证:真实的隔离判据是sys.prefix和sys.base_prefix之间的差值

实测 · 本机执行输出
系统python3:
  sys.prefix       = /opt/homebrew/opt/python@3.14/Frameworks/Python.framework/Versions/3.14

venv里的python3:
  sys.prefix       = .../py-lab/venv-test          ← 真实变成了venv自己的目录
  sys.base_prefix  = /opt/homebrew/opt/python@3.14/Frameworks/Python.framework/Versions/3.14   ← 真实还是指向系统解释器

venv启动时真实做的核心动作,是把sys.prefix(决定去哪找site-packages)指向venv自己的目录,但保留sys.base_prefix指向原本的系统安装——不在venv里的解释器,这两个值真实是相等的;一旦进了venv,这两个值真实会不一样。这正是很多工具(包括pip自己)判断"当前是不是在虚拟环境里"最常用的真实依据:sys.prefix != sys.base_prefix,不需要额外读环境变量。

验证:venv真实是从零开始的空目录,装的包真实互不可见

实测 · 本机执行输出
venv-test/bin/pip list(刚创建,还没装任何包):
  Package Version
  ------- -------
  pip     26.2.1        ← 真实只有pip自己,不是系统site-packages的复制品

venv-test/bin/pip install tabulate
venv-test/bin/python3 -c "import tabulate; print(tabulate.__file__)"
→ .../venv-test/lib/python3.14/site-packages/tabulate/__init__.py    ← 真实装进了venv自己的目录

系统python3 -c "import tabulate"
→ ModuleNotFoundError: No module named 'tabulate'    ← 真实系统解释器完全看不到这个包
这组对比是本文认为"依赖隔离"最直观的证据:新建venv的pip list真实是空的(只有pip自身),说明它不是像某些语言的包管理那样"链接"到一份共享的全局包仓库,而是真实拥有一份独立的site-packages目录;在venv里装的tabulate,其文件真实落在venv-test/lib/python3.14/site-packages/下,系统Python的模块查找路径里根本不包含这个目录,所以import真实找不到它。这套隔离机制的价值很直接:一台机器上的不同项目可以真实依赖同一个库的不同版本而互不冲突,项目A装的是requests==2.x,项目B装requests==3.x,各自的venv里各装各的,谁也不会覆盖谁的全局环境——本系列后面讲pip依赖解析的文章会接着验证"装依赖"这一步本身真实是怎么解出版本号的。

分步演示:venv隔离机制真实的四层证据

每一步都是本文上面用真实创建的venv跑出来的原始结果。

从pyvenv.cfg到prefix分离再到真实互不可见的包
这一步新验证的机制

    本文用到的工具

    • 本机 Python 3.14.7,真实的 python3 -m venv 与 pip install
    京ICP备2026067502号-1京公网安备11010502063487号