kuanat

kuanat

V2EX 第 634702 号会员,加入于 2023-06-19 11:38:40 +08:00
根据 kuanat 的设置,主题列表被隐藏
二手交易 相关的信息,包括已关闭的交易,不会被隐藏
kuanat 最近回复了
42 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@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 解释器和源码都不需要做任何改变,完全向后兼容。
44 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@flyingghost 60

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

另一个方面,一个包 A 如果同时依赖 B 和 C ,我猜测不存在一个多项式算法,可以从 B 和 C 依赖计算出 A 的依赖。(需要 backtrack 到所有依赖重新计算)导致这样的缓存中根本上不可用。
我的映射是 capslock 短按是 esc 长按是 ctrl 。
44 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@cnt2ex #57

这里我说得太简略了。

在设计这个 import 机制的 1.x 版本,要实现 import * 需要去文件系统里遍历有哪些模块。底层文件系统可能会对某个名为 xyz 的模块,返回 XYZ/Xyz/xyz 几个不同的结果。当时对于这个问题的解决方案是设计 __all__ 让维护者自己声明是哪一个。

之后的 PEP 才明确了模块名应该( should )全小写,import 的所有符号都是小写。再之后 __all__ 才成为一种工程上的控制机制。
44 天前
回复了 Goooooos 创建的主题 Java 吐槽下 Google 开源的组件
@Knife42 #8

自我定位是 long time lurker 所以没有什么社交账号。

v2ex 发帖的初衷是想在 AI 生成内容的时代留下一点有价值的东西。
45 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@frostming #54

我又看了一遍自己写的东西,其实不太准确,我这重新描述一下。

版本选择之所以是 NP 问题,源于好几个假设。其中有几个假设是没法改变的,能够改变的还有两个:

- 一个包可以声明自己依赖零个或多个特定版本的包。
- 不允许同时选择同一个包的两个不同版本。

第一条过于严格了。如果把特定版本换成一个范围,这个问题就简单多了。第二条也可以适当放宽,和第一条一样,都是建立在 semantic versioning 主动向后兼容的假设之上。虽然不能引入同一个包的 1.X.X 和 1.Y.Y ,但是可以同时引入 1.X.X 和 2.Z.Z 。

这两个条件放宽之后,NP 问题就不存在了。但是对 APT/RPM/Node/Python 这些起步较早的社区来说,这两个要求依然太难了。

至于主包覆盖依赖的限制,这是用来解决菱形依赖的,不是规避 NP 问题的核心措施。还是之前的例子,只要保证 C 符合 semantic versioning 向后兼容就好了。这个措施实际体现的是解决问题的思路转变。

具体的来源比较分散记不清了,我凭印象做个总结。第一个做 NP 规避的应该是 Rust/Cargo 。之后 Go 做了一个叫 Dep 的类 Cargo 实现,实践了一段时间,吸取经验教训,重做之后形成了正式的 Go mod 方案。Rust/Go 用相同的方法解决菱形依赖,但是基础思路是不一样的。这一点从 Rust 总是选择最高版本,而 Go 总是选择最低版本能看出来。

这里想象一个场景:A==1.1.0 依赖 B==1.5.0 ,之后 B 发布 B==1.6.0 ,由于 B 引入了非兼容改变,导致 A==1.1.0 无法依赖 B==1.6.0 构建。这个时间节点,按照 Rust 的设计,所有依赖 A 的用户(包括依赖 A 的老用户)都会受到影响,而 Go 这边只有同时依赖 A==1.1.0 和 B==1.6.0 的新用户(老用户不受影响)才会受影响。

之后 A/B 至少有一方要打补丁发新版。Rust 认为要么 A 在 B 没有提供补丁的情况下,主动发新版声明不兼容 B==1.6.0 ,要么 B 发新版修复对 A 的支持。实际上在开源社区这两个事情都是很难的。Go 的设计是除非用户主动升级,都会保持作者发布时的最低依赖版本,(毕竟发布那个时刻的版本依赖几乎是都可以构建的)这样就给 A/B 争取了非常多的修复时间。

Rust 的设计者更希望为开发者提供最好的体验,希望一己之力解决所有问题。Go 的设计思路是依靠社区合作,依赖所有人主动去帮助解决对自己来说比较容易,而别人不好解决的问题。所以你能看到,Go 官方一直不遗余力地推动向后兼容,因为 Go 的整套实现逻辑都强依赖整个社区对于兼容性规范的共识。



最后赞美 pnpm ,感谢!
45 天前
回复了 hankli 创建的主题 程序员 试一下用 VersionFox 替代 asdf-vm?
@hankli #7

我一开始没注意到 VersionFox 是跨全平台的。为了兼容 Windows/PS 的话,确实单可执行文件比脚本靠谱。(我觉得理智一点的开发者,在 Windows 环境不用 WSL ,也应该用 MSYS2 吧哈哈)
46 天前
回复了 hankli 创建的主题 程序员 试一下用 VersionFox 替代 asdf-vm?
支持一下~

asdf shell 一直都有,global/local/shell 都支持。我记得 shell 的实现比较粗暴,就是类似 ENV=xxx cmd 的方式。

shim 机制的问题在于需要 reshim ,比如 python pip 安装了某个可执行文件,需要 reshim 才能在当前 shell $PATH 索引到。极小概率的情况,比如某些程序运行后释放执行脚本,又硬编码 #!env python 这样会导致出错。

asdf 有个 direnv 插件,可以管理环境变量,解决每次执行都要 shim 查找的问题。(话说慢真是个问题吗?)

asdf 的优点是插件可以比较方便复用已有的构建脚本,本地构建安装而不是下载二进制文件。

另外我个人比较喜欢这种脚本都用 git 做管理的模式。
46 天前
回复了 bocchi1amos 创建的主题 Python 为什么 Python 会有.venv 虚拟环境的概念?
@kuanat #47

再做一点补充。

Python 3 是 2008 年左右发布的。这个时间点上,如果 Python 决定重写 import hook ,将版本号纳入成为包名的一部分,支持安装同一个包的多个版本,就没有今天虚拟环境什么事了。

这个改动和“不做官方包管理”不冲突。不做官方包管理是正确的选择,在那个时间点上,很难做得好,特别是这些实现都要用 C 来写。

但是底层限制所限,同一个解释器环境不能安装同一个包的多个版本,那包管理器是永远无法摆脱虚拟环境的。
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1384 人在线   最高记录 6543   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 11ms · UTC 17:09 · PVG 01:09 · LAX 09:09 · JFK 12:09
Developed with CodeLauncher
♥ Do have faith in what you're doing.