为什么 Python 会有.venv 虚拟环境的概念?

2024 年 1 月 10 日
 bocchi1amos


本人刚从 java 、c#转过来,因为工作需要,这两天开始研究 python 工程。
(其实以前也接触过,但只限于会写些代码层次的基础语法)
现在新人有个疑惑,为什么 python 会有.venv 这种概念,资料说是项目编译环境隔离,每个项目有自己的解释器环境什么的。其实这种给每个项目搞内环境部署,很像是 java 开发里给每种服务的 docker 容器都单独打包一个独立的 jdk ?

11411 次点击
所在节点    Python
69 条回复
flyingghost
2024 年 1 月 13 日
@kuanat 不懂就问,包依赖的计算确实是一个无法快的计算,那么有没有人考虑过由中央包管理服务器来做这部分计算结果的缓存?这样在固定条件下,大部分包的使用者就会受益。
我不太会估算这个缓存的存储成本,但感觉查询成本会快于本地计算哎。
谢谢解惑。
kuanat
2024 年 1 月 13 日
@flyingghost 60

我没有听说过类似的方案,如果让我评论的话,我认为两个方向都不合适,一个是结合了版本号之后命中率很

另一个方面,一个包 A 如果同时依赖 B 和 C ,我猜测不存在一个多项式算法,可以从 B 和 C 依赖计算出 A 的依赖。(需要 backtrack 到所有依赖重新计算)导致这样的缓存中根本上不可用。
cnt2ex
2024 年 1 月 14 日
@kuanat #58

我的疑问是,__all__和你提到的文件系统大小写敏感几乎没有关系。因为解决大小写不敏感所带来的问题的方案主要是通过规范要求名称都是小写。而且不管有没有__all__,import *都可以使用,只不过没有__all__会默认导入所有非下划线开头的名字。但正是这一点,让我很疑惑为什么要扯上__all__。也许你提这个目的单纯是想说__all__的诞生的历史是在规范模块名之后提出的?除此之外我看不出__all__和文件系统大小写不敏感有什么关系。

正是前面这一点我没看懂,所以之前还有一点也没继续问下去。其实还有一个问题就是,你#48 提到:
> 这个时间点上,如果 Python 决定重写 import hook ,将版本号纳入成为包名的一部分,支持安装同一个包的多个版本,就没有今天虚拟环境什么事了。
为什么这样能解决需要虚拟环境的问题?按我所理解的,go 之所以能把所有 mod 都哦下载到$GOPATH/pkg 下,然后在加上版本区分,完全是因为二进制文件能够记录编译时的版本号(说的更完整一些,完全也可以静态编译)。

而对于脚本型的语言,没有一个地方记录库对应的版本,因此这类语言采取的方式都是设置对应的 PATH (比如 PYTHONPATH 等等),然后在 PATH 中寻找最先遇到的库的版本导入。如果你要多版本共存,在同一个 PATH 下,遇到同个包的两个版本,该导入哪个?
kuanat
2024 年 1 月 15 日
@cnt2ex #63

我的描述是基于自己的记忆和理解,不一定就是正确的。

我原本是想要通过 __all__ 这个例子来体现,基于文件系统的 import path 对今天包管理机制造成的困扰。

在当时 windows/dos/Macintosh 的文件系统上,import * 遍历出来的文件名是大小写不固定的,也就导致了引入的符号表大小写不固定。于是设计了 __all__ 机制,因为 __all__ 定义的大小写是可控的。开发者可以选择 opt in 。

__all__ 机制是需要手动维护的,也不能阻止用户显示地手动引入。如果今天重新设计一门语言,__all__ 是完全不必要的,可以通过 public/private 关键词,也可以通过某种 name convention 来实现。

并不是说基于文件系统的 Package 组织形式不好,而是说这是一个选择。Python 选择了简洁,但是要在别的地方(包管理)付出代价。__all__ 机制就是这个代价之一。


回到虚拟环境的问题上,现在假想一下 Python 依旧使用基于文件系统的 Package 组织形式,但是在路径名中包含版本号。

现在 Python 就面临几种选择,要么以 import xyz==1.1.1 的形式使用,要么 Python 自己实现一个版本选择机制然后维持 import xyz 的向后兼容。

第一种方案几乎没有人选的。因为一旦要升级依赖,需要显示地在源代码里面做修改。但是这个方案的改版非常多见,比如 npm 的 package.json 就是集中把 xyz==1.1.1 放到了一起。

(看起来很完美对吧,但是当时没有人意识到这个 NP 问题会成为日后的麻烦。这种组织方式从机制上就不允许引入同一个包的不同版本,所以 NP 问题退化为 P 问题的途径就少了一个。)

第二种方案,考虑到时间节点,不是一个好的选择。但这个路线是近十年来以软件工程为导向的语言的首选。

这里需要明确区分 Package 的组织形式和 import path 的实现,前者是一种 specification 规范,后者是一个实现方式。Python 完全可以从规范层面要求所有的包名都带版本号,然后 import hook 的实现无视它,固定使用版本号最大或者最小,甚至文件最后修改信息最新或者最旧的版本。


虚拟环境解决的是:在 Package 组织形式是基于文件系统、且 Package 规范不包含版本号的前提下,提供一种在同一个系统中,安装同一个包多个不同版本的可能性。

如果 Package 规范要求包名带版本号,那么“安装”这个行为是不受限制的,也就不需要虚拟环境了。至于安装好了之后,使用时“选择“哪个版本,这是另一个事情。

技术层面上,我猜 Python 转向 npm 类似的包管理方式是没有太大难度的,PEP 621 规范了 pyproject.toml ,也就是前面说的方案一,楼上 frostming 开发的 PDM 就是这么做的。现在的问题是 Python 的 import hook 本身并不支持路径名带版本号的,所以还是需要再套一层虚拟环境,让 Python 只能访问到特定的包。

说得再具体一点,包管理器在文件系统里,以包名+版本号的形式,集中管理所有的包。虚拟环境指定了 $PATH ,在当前环境中的包软链接到实际的带版本号的包,然后软连接的名字不包含版本号。包管理器额外维护一个 pyproject.toml 记录当前所用的包,同时负责计算依赖。在此环境内的 Python 解释器和源码都不需要做任何改变,完全向后兼容。
cnt2ex
2024 年 1 月 15 日
@kuanat #64

> 如果 Package 规范要求包名带版本号,那么“安装”这个行为是不受限制的,也就不需要虚拟环境了。至于安装好了之后,使用时“选择“哪个版本,这是另一个事情。
所以我对“如果 Python 决定重写 import hook ,将版本号纳入成为包名的一部分,支持安装同一个包的多个版本,就没有今天虚拟环境什么事了”这句话产生疑问。
无论怎么重写 import hook ,又或者在包里加上版本区分。仅仅是在“被导入方”加入版本信息还不够,还需要在“导入方”记录要导入的版本才行。
脚本型语言和编译型语言不一样,不存在编译、运行两个过程,编译型语言可以在编译时,在二进制文件里记录版本信息,那脚本型语言在哪里记录版本号就是个问题了。

> Python 完全可以从规范层面要求所有的包名都带版本号,然后 import hook 的实现无视它,固定使用版本号最大或者最小,甚至文件最后修改信息最新或者最旧的版本。
你所说的几种情况,一旦某个依赖的包升级,就有可能导致已有项目崩溃。且不说 lib.py.1.0 被 lib.py.2.0 取代,会导致依赖 lib.py.1.0 的项目崩溃。
即使只是 lib.py.1.0.1 被 lib.py.1.0.1 取代,这种崩溃也是可能的,semver 无法解决这个问题。因为很多时候你无法区分 breaking, enhancement 和 bugfix 。
用别人的例子, [你在你的包里加入了一个 warning ,这个是属于 breaking, enhancement 还是 bugfix ?]( https://twitter.com/brettsky/status/1262077534797041665)
这里选择 bugfix 的是最多的,可你又如何保证你新输出的 warning message 不会 break 别人的项目呢?所以 semver 本身是无法被依赖的。你无论再怎么设计规范,总会出现你的 bugfix 成为别人的 breaking 的情况。

其实我想说的重点是,多版本共存本身不是问题所在。反而由于如果强行多版本共存,在运行时,要如何“选择”哪个版本是主要问题。而为了解决这些问题,又进一步会带来一堆问题。

所以,为什么非要多版本共存?没有多版本共存本身就是一种解决方案,而不是问题。
cnt2ex
2024 年 1 月 15 日
> 即使只是 lib.py.1.0.1 被 lib.py.1.0.1 取代
修正一下:即使只是 lib.py.1.0.0 被 lib.py.1.0.1 取代
eailfly
2024 年 1 月 17 日
我到觉得 venv 这种方式更爽,新建项目的时候在本目录创建一个.venv ,不搞了直接一个 rm -rf 清爽干净,bundle 好是好,就是有时候试用或者玩玩一个 gem ,过后清理吧可能有依赖剩下,不清理吧一直放那也不用,也可能我还没领悟到 bundle 的精髓吧。。。
scguanzhong
2024 年 3 月 21 日
我也觉得 venv 很清爽啊 ,不需要直接删除就 ok 了
timerring
2025 年 6 月 14 日
@kuanat 大佬怎么看待最近很火的 uv ?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://v2ex.ih06.com/t/1007645

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX