小何那天在CI流水线上卡了整整四十分钟,屏幕上反复停在同一行:
python -m venv .venv
他以为CI服务器卡死了,重启了一次,又等了二十分钟,还是停在那儿。后来他把命令换成 virtualenv .venv,三秒就结束了。
“同样是建虚拟环境,凭什么一个要几十秒,一个只要三秒?”
更早之前,他还遇到过另一个问题:公司服务器上装了三个Python版本,他想用3.8建环境,敲 python -m venv -p 3.8 .venv,直接报错说不认识 -p。换成 virtualenv -p 3.8 .venv,一次成功。
小何一直以为 venv 和 virtualenv 就是“同一个东西的两个名字”,顶多写法不同。直到这两个问题同时砸过来,他才意识到:它们根本不是同一个东西,一个是标准库里的“基础款”,一个是第三方库里的“增强款” 。
先看一个最直观的对比
# venv:Python 自带,不需要安装
python -m venv .venv
# virtualenv:需要单独 pip install
virtualenv .venv
venv 从Python 3.3开始就是标准库的一部分,装了Python就有,不需要额外安装任何东西。virtualenv 是一个独立的第三方包,要先 pip install virtualenv 才能用。
用一个生活场景来理解。venv 是你房子里本来就有的工具箱,虽然工具不多,但随时能用。virtualenv 是你额外买的一套专业工具,功能更全,但前提是你得先花钱把它买回来。
但“自带”和“要安装”只是表面差异。真正的区别,藏在下面几个地方。
核心区别一:创建速度,差了一个数量级
venv 建一个环境,可能要几十秒;virtualenv 通常不到一秒。这不是网络问题,也不是机器性能问题,而是两者“初始化pip”的方式根本不同。
venv 的种子方式是**启动一个子进程跑 pip**,让pip自己去安装自己需要的组件。这个过程涉及文件复制、pip初始化、索引查询,在Windows文件系统上尤其慢。
virtualenv 用的是 app-data 种子缓存:它预先把pip、setuptools、wheel的wheel文件缓存在一个共享目录里,建环境时直接从缓存“展开”,不需要联网,不需要启动子进程,所以后续创建可以快到1秒以内。
用一个生活场景来理解。venv 每次做菜都要从零开始烧水、洗菜、切菜,前面准备就要花很久。virtualenv 提前把常用食材预处理好放在冰箱里,开火就能炒。
这个差异在CI/CD流水线里会被放大。你的流水线每跑一次就要建一次环境,venv每次多花三十秒,一天跑一百次就是五十分钟的纯等待。小何遇到的第一个问题,根源就在这里。
核心区别二:Python版本发现能力
venv 只能使用运行它的那个Python解释器来建环境。你敲 python3.11 -m venv .venv,建出来的就是3.11的环境。它不支持“我当前是3.11,但我想建一个3.8的环境”。
virtualenv 可以自动发现系统上安装的任意Python版本,并且允许你通过 -p 参数指定版本号或解释器路径:
virtualenv -p 3.8 .venv
virtualenv -p /usr/bin/python3.8 .venv
venv 没有 -p 参数。要用其他版本,你只能先调用那个版本的解释器去执行 -m venv,或者手动指定完整路径。在多版本共存的服务器上,这个限制会让脚本变得很啰嗦。
小何遇到的第二个问题,就是因为把 virtualenv 的参数习惯带到了 venv 上。venv 根本不认识 -p。
核心区别三:venv 是“子集”,不是“另一个实现”
很多人以为 venv 和 virtualenv 是两套平行实现,只是作者不同。实际上,**venv 是 virtualenv 的一个子集**,被集成进了标准库。
子集意味着:venv 保留了最核心的建环境能力,但砍掉了 virtualenv 的很多附加功能。
没有插件系统。 virtualenv 允许第三方通过插件扩展发现、创建、种子、激活等环节,venv 没有这个机制。
没有完整的编程API。 virtualenv 提供了一套完整的Python API,可以在不真正创建环境的情况下“描述”一个环境,tox、poetry、pipx 这些工具都依赖它。venv 只有一个基础的 create() 函数。
升级方式不同。 venv 的升级跟着Python版本走,你换了Python,venv 才跟着变。virtualenv 通过 pip install --upgrade virtualenv 独立升级,和Python版本解耦。
换句话说,venv 是“够用就好”的稳定版,virtualenv 是“功能更全、迭代更快”的完整版。
那隔离性有区别吗?默认都没有
这是一个常见的误解:以为 virtualenv 隔离更彻底,venv 会“漏”到系统环境。
实际上,两者的默认行为是一致的:新建的环境默认都不包含系统的 site-packages,你 pip install 的东西只进当前虚拟环境。venv 也提供了 --system-site-packages 选项,如果你显式加上,它才会让环境能看到系统的包。
所以隔离性的差异不在“默认可不可靠”,而在灵活性。virtualenv 在种子、激活、发现等方面提供了更多可配置的钩子,venv 的选项相对固定。对于绝大多数项目,venv 的默认隔离已经完全够用;但如果你需要更细粒度的控制(比如自定义种子版本、特殊的激活脚本逻辑),virtualenv 的插件体系才能满足你。
坑:pip 和 setuptools 的“捆绑”不是永恒的
很多人对 venv 的信任来自一句老话:“venv 会帮你装好pip,开箱即用。”这句话在Python 3.12之前基本成立,但3.12之后变了。
从Python 3.12开始,venv 创建的环境只默认安装pip,不再默认安装setuptools。virtualenv 也跟随了这一变化。如果你的旧代码或旧工具链依赖 pkg_resources、distutils、easy_install 这些setuptools带来的东西,在新版 venv 里会直接找不到,需要手动 pip install setuptools。
而 virtualenv 虽然也跟随了“不默认装setuptools”的决策,但它内置的pip/setuptools/wheel wheel版本通常比 venv 捆绑的新,因为它通过自己的发布周期独立更新这些种子包。如果你需要“一建好环境就有一个比较新的pip”,virtualenv 的默认种子状态往往更省心。
到底该用哪个?
判断标准可以归结为三个问题。
你能安装第三方工具吗? 如果你的环境受限,只能使用Python标准库里的东西(比如某些企业内网、精简容器),venv 是唯一的选择,因为它不需要任何额外安装。
你需要自动发现其他Python版本吗? 如果你经常在一台机器上为不同版本建环境,virtualenv -p 的便利性是 venv 给不了的。
环境创建的频率高吗? 如果是CI流水线、频繁重置的容器、或者一天要建几十次环境的开发流程,virtualenv 的app-data缓存带来的速度优势是实打实的。但如果只是本地项目偶尔建一次环境,几十秒的差距可以忽略。
如果以上三个问题都是“否”,**venv 就是更合适的选择。它是标准库的一部分,没有额外依赖,行为稳定,官方文档也明确推荐将它作为创建虚拟环境的标准工具。virtualenv 的价值不在于“它比 venv 更好”,而在于它覆盖了 venv 覆盖不了的那部分场景**:多版本发现、插件扩展、频繁高速创建、以及编程API集成。
回到小何的问题
小何最终的方案不是二选一,而是按场景分工。
CI流水线里用 virtualenv,因为创建频率高,速度优势明显。本地开发用 venv,因为不需要额外安装,一行 python -m venv .venv 就够了。需要为某个旧项目建Python 3.8环境时,单独用 virtualenv -p 3.8,不再试图让 venv 去做它做不到的事。
他后来在文档里写了一条:“venv 是标准库自带的基础款,零安装、够用、稳定。virtualenv 是功能更全的第三方增强款,快、能发现多版本、有插件和API。不要因为它们都叫‘虚拟环境’就混着用,也不要因为它们能建出看起来一样的环境就以为它们行为一样。 选哪个,取决于你的场景需要的是‘零依赖’,还是‘快和多版本’。”
venv 和 virtualenv 的区别,本质上是“标准库的稳定子集”和“功能完整的第三方超集”之间的区别。它们解决的是同一个问题,但服务的是不同约束下的需求。