这不是我第一次写 Pipenv 相关的文章,也相信不是最后一次,前两篇我用的是英文,(浅陋地)分析了 Pipenv 和 Poetry 的优劣,至今仍是我博客访问量最高的文章。今天是因为在知乎上看到两位朋友写的两篇文章(链接我放在文末了),吐槽了一通以后推荐大家不要使用 Pipenv。说实话,作为核心维护者之一我是有点心酸的,因为他们说的那些问题的确都存在。在本文中我希望从一个核心维护者的角度,总结一下 Pipenv 存在的问题,作为一个告解。

从我关注 Issues 列表以来,我脑中能回想起来的,抱怨频率最高的,也是最影响用户体验的,有几个问题:

1. Lock 时间长

用户经常抱怨pipenv lock的时间长,特别是涉及到一些科学计算的库时,如numpy, sklearn, tensorflow,会慢得让你怀疑人生。pipenv lock其实做的就是依赖解析,而慢的原因是,Pipenv 需要下载所有的安装包来计算它们的哈希值,要命的是,像numpy这种库,一个版本就有17 个包,每个包的大小是 10M~20M 不等,总共下载的大小就有 300 多 M 左右。也有人提 PR 希望修改这个逻辑,但后来都不了了之。

Issue 传送门:https://github.com/pypa/pipenv/issues/3827

PR 传送门:https://github.com/pypa/pipenv/pull/3830

2. 命令及选项的结果不符合预期

李辉老师的文章里面列举了安装、卸载、更新包的问题,我这里先回复一下,其实它们都是同一个问题:pipenv update不能保护其他包不被更新。并且--keep-outdated--selective-upgrade这两个选项意义不明容易让用户搞错。

其实--keep-outdated有一次大修复只是还没有发布到新版本,所以用 github 上的 master 分支是没问题的。我在这里解释一下--keep-outdated--selective-update这两个选项的作用:--keep-outdated意思是更新 Pipfile.lock 时,不会删除已经不需要的依赖。这对于产生一个跨平台的 lock 文件非常有用,因为有些仅 Windows 需要的依赖,你在 Windows 上生成 Pipfile.lock 时会有,而换到 Linux 上再执行pipenv lock时就没有了。这个选项时针对 Pipfile.lock 更新的,而--selective-upgrade是针对安装过程的,它会控制 pip 安装包时,只在有必要的时候升级次级依赖的版本。这里又涉及到一个逻辑的不统一:用pipenv install xxx安装包的时候会先调用pip install xxx,并用 pip 的机制去更新依赖,再用 Pipenv lock 去锁定依赖。理想情况下,依赖解析器应该唯一,应该通过 Pipenv 解析完了以后再统一安装。

除此之外,其他的一些不符合预期的命令和混乱的选项有:

  • pipenv install--skip-lock, --ignore-pipfile, --deploy,此外还有不更新 Pipfile.lock 的pipenv sync命令,有谁能一眼区分出它们各自的作用?
  • 安装普通依赖用pipenv install,安装普通和开发依赖用pipenv install --dev,但pipenv lock永远一起解析普通和开发依赖,有没有--dev都一样。然而pipenv lock -r是生成普通依赖,pipenv lock -r --dev是仅生成开发依赖。
  • 接上一条,pipenv uninstall --all是删除当前虚拟环境中所有已安装的包,不更新 Pipfile,而pipenv uninstall --all-dev是删除所有开发的依赖,更新 Pipfile

我说的这些问题,都对应着许多 Issue,我就(lan)不(de)一一列举了。

3. 无法解析依赖

这一点也是在Poetry 的文档中作为反面教材抨击的,其根本原因是,Pipenv 不能自动回溯依赖的版本来满足依赖的限制。比方说 A 包依赖 C<1.0,而 B 包的 1.x 版本依赖 C<1.0 而 2.0 版本依赖 C>=1.0,那么你在 Pipfile 中同时包含 A, B 时就会解析失败:Pipenv 只会选用 B 的最新版本,在依赖不能满足时不会尝试旧版本。

Pipenv 解析依赖其实用的是piptools,后者不能解析的 Pipenv 也不能。好消息是 Pipenv 维护小组做了一个新的依赖解析器passa,还在试验阶段,它能解决这个问题,未来会替代成为 Pipenv 的依赖解析器。

4. 怎么还不 release,以及其他的开发流程的问题

董伟明老师说到 BDFL,没错,Kenneth Reitz 曾经设计出了一套 PEEP 的流程,并把自己放在 BDFL 的位置上。但是,由于他本人对开源热情的消退,他已经实际上退出了这个位置。但是 PEEP 的机制仍然存在:所有 Behavior change 必须先落地到 PEEP 文档上,其实 PEEP 不难写,能说清楚就行了,但是从这个机制创立至今,仅有 5 个 PEEP 被接受且没有外部贡献者的 PEEP,这其中,又仅有 2 个涉及机制变化的 PEEP 真正落地。

其实 Pipenv 的问题数量不算多,维护者的人力对比 Poetry 也不见得少,关键问题就是上述的几个严重影响用户体验的问题,或者问题修复了却迟迟不发布新版。

不止一个人,也不止一次有人抱怨发布无限延期的问题,就快变成陈永仁那个「说好了三年,三年之后又三年,如今都快十年了!」的梗了,我在这里也解释一下。现在核心维护者主要有 Dan Ryan(techalchemy), Tsuping Chong(uranusjr)和我,其中只有 Dan 有 PyPI 的权限,我其实说白了就是个「比较勤奋的 Contributor」的角色。而 Dan 因为私事缠身无暇顾及开源工作。但好消息是他自称事情已处理得差不多,会慢慢跟上进度。虽然我知道催促一个维护者在开源社区中不是一个礼貌的做法,但我也理解大家的心情,以及因此而心灰意冷弃用的用户,所以我恳请大家,宽容一些,静静等待吧。

为什么不开放权限给其他人?比如说我。

Dan 是一个严谨的人,他希望亲自过一遍改动日志,润色完了以后再发布,所以还需要等待一些日子。他也对新特性的态度非常保守,总是害怕影响 regression,破坏已有用户的体验。具体可以看看这个 PR里面的回复,这一点我不能认同,但也无可指责,毕竟他承担了 Kenneth Reitz 以后 90%的开发工作,其中有些部分确实非常棘手和麻烦。

作为维护者之一,我的想法是,因为 master 上已经积压了太多的改动,先等 Dan 回来把这次新版本发布了,回到正轨以后,我会开始针对以上我提到的问题,编写 PEEP,引入 Deprecation 机制,不去回避 Breaking change。

5. Poetry 如何呢

最后还是提一下 Poetry 吧。Python 的工作流工具,其实无非是解决三个方面的问题:虚拟环境管理、依赖管理、打包发布。Pipenv 只包含前两项,比重是 50%:50%,而 Poetry 同时包括三项,比重是 20%:40%:40%。所以当我用惯了 Pipenv 切换到 Poetry 时会非常不习惯——它对于虚拟环境的控制太弱了:我无法知道我用的是哪个环境,路径是什么,也不能随心所欲地删除、清理、指定虚拟环境的位置。Pipenv 的依赖解析器确实存在很多问题,但 Poetry 的也离完美有一段距离。而且 Poetry 负责的打包发布部分,也不是最好的。所以我认为 Poetry 也没有大家推荐的那么好。如果 Pipenv 没有满足你的要求,那么虚拟环境管理方面我推荐virtualenvwrapper+direnv(这两个的最大问题是不支持 Windows),依赖解析方面我推荐piptools,打包发布还是用 setuptools。

相关阅读

  1. 李辉:不要用 Pipenv
  2. 董伟明:也谈「不要用 Pipenv」
  3. Python packaging war: Pipenv vs. Poetry
  4. A deeper look into Pipenv and Poetry