不要用 Pipenv

665 阅读5分钟

Pipenv 让我用的很痛苦,有一种被欺骗的感觉,而且后悔在《Flask Web 开发实战》里采用它。

大部分情况下,它很好用,但却存在太多问题,有一些问题让人简直没法接受。我知道有人会说「这是开源程序,有 bug 就自己去修」、「爱用不用,没人强迫你」,但问题是,一个进行大肆推广,甚至借 PyPA 做背书来宣传(经常让人误以为是 Python 官方推荐)的工具却连基本的使用流程都没做好,这不是合理和正常的行为。 引用这个 HN 评论的话说就是:

Kenneth Retiz 滥用他在 PyPA 的位置(而且快速把一个实际上是 beta 状态的产品的版本号从 0 升到 18)来暗示 Pipenv 已经非常稳定,受到大力支持并且非常官方,但事实却并不是这样。

在这篇(劝退)文章里,我会分别从包的安装、更新、卸载来测试并指出 Pipenv 的一些问题。

测试准备

  • 项目:Bluelog(一个 Flask 博客)
  • Pipenv 版本:2018.11.26(最新版本)
  • 操作系统:Windows 10,Cmder
  • 测试流程:每一次测试命令前都会删除已经创建的虚拟环境(pipenv --rm),重置 Pipfile 和 Pipfile.lock 文件变动,然后重新创建虚拟环境(pipenv install)。

安装包

假设想给这个旧项目 Bluelog 添加新功能,拿到旧项目的代码,打算安装一个 Flask-Avatars 包。查了文档,发现安装包要使用 pipenv install 命令:

https://docs.pipenv.org/en/latest/basics/#pipenv-install

所以执行了下面的命令:

$ pipenv install flask-avatars

结果发现其他所有的不相干依赖都被更新了……

WTF,这不是反人类吗(说好的「Python Development Workflow for Humans.」呢)?我安装一个包,默认行为竟然是更新其他所有不相干且已经锁定版本的依赖!

翻了文档才发现,要加一个 --keep-outdated 选项才能避免更新其他锁定的依赖:

$ pipenv install --help
  ...
  --keep-outdated          Keep out-dated dependencies from being updated in
                           Pipfile.lock.  [env var: PIPENV_KEEP_OUTDATED]

好吧,那先忍着,多打一个命令行选项就是了:

$ pipenv install --keep-outdated flask-avatars

WTF,为什么所有依赖还是被更新了?

好吧,有 bug 很正常,我来提个 issue 吧,哎,好像有很多 issue 了?

重点评论:

https://github.com/pypa/pipenv/issues/1554#issuecomment-370850330

Kenneth Reitz 先是说 lockfile 只要是过期了就总是会被重新生成(这是什么逻辑?),接着又说用 pipenv update depname,但其他人都回复不起作用(我下面会进行单独测试)。

接着,看到其他评论提到用 --selective-upgrade 选项:

$ pipenv install --help
  ...
  --selective-upgrade      Update specified packages.

我又继续使用 --selective-upgrade 选项:

$ pipenv install --selective-upgrade flask-avatars

仍然会更新所有依赖……

对了,顺便还测试了这个命令,依然没用:

$ pipenv install --keep-outdated --selective-upgrade flask-avatars

除了安装某个包会导致所有依赖版本被更新,Pipenv 在解决依赖的冲突上面也有一些不足,比如执行下面的安装命令(具体见 Poetry README):

$ pipenv install oslo.utils==1.4.0

会提示无法安装成功:

ERROR: ERROR: Could not find a version that matches pbr!=0.7,!=2.1.0,<1.0,>=0.6,>=2.0.0

更新包

假设我想更新 Bluelog 这个项目用的 Flask 版本(从 1.0.2 更新到最新的 1.1.1)。查了文档,找到了 update 命令,文档是这样写的:

https://docs.pipenv.org/en/latest/basics/#example-pipenv-upgrade-workflow

于是我执行下面的命令:

$ pipenv update flask
Locking [dev-packages] dependencies…
Success!
Locking [packages] dependencies…
Success!
Updated Pipfile.lock (fd55e3)!
Installing dependencies from Pipfile.lock (fd55e3)…
  ================================ 26/26 - 00:00:12
To activate this project's virtualenv, run pipenv shell.
Alternatively, run a command inside the virtualenv with pipenv run.
All dependencies are now up-to-date!

突然看到最后一行赫然写着「All dependencies are now up-to-date!」,我以为是搞错了,赶紧看了下 Pipfile.lock,WTF,为什么我所有的依赖(包括和 Flask 完全不相关的)又都被更新了?

依然,已经有很多相关 issue:

重点评论:

https://github.com/pypa/pipenv/issues/966#issuecomment-346204439

如果这个 issue 没有被锁定,这一句「I have no idea.」下面的图标不知道还会被点多少次。我猜 Kenneth Reitz 对这个 issue 让多少人头疼也没有 idea。

继续搜索,查文档,发现 update 命令也有 --keep-outdated 和 --selective-upgrade 两个选项:

$ pipenv update --help  
  ...  
  --selective-upgrade      Update specified packages.
  --keep-outdated          Keep out-dated dependencies from being updated in
                           Pipfile.lock.  [env var: PIPENV_KEEP_OUTDATED]

先来试下 --keep-outdated:

$ pipenv update --keep-outdated flask

no luck,还是更新了所有依赖。继续试一下 --selective-upgrade:

$ pipenv update --selective-upgrade flask

依然没用,仍然会更新所有依赖……

继续查 issue,发现下面这些:

在 #3461 里发现了下面这个评论:

https://github.com/pypa/pipenv/issues/3461#issuecomment-455740272

(因为 Frost Ming 是国内的同学,也是核心维护者,说明一下,这里无意冒犯,引用这个评论只是想说明 Pipenv 现在的开发状态。)

这段评论的重点是「In fact, the package name passed as argument is not used at all.」。

也就是说,pipenv update 实际上是不接受包名称参数的。这在下面这个评论也得到了印证:

Here is the important caveat:pipenv updatealwaystargets every package in your lockfile, without exception. It does not accept arguments.

https://github.com/pypa/pipenv/issues/3461#issuecomment-493847853

一个还没实现的功能就写到文档里了?这真的不是开玩笑吗?不仅是写到了文档里,还写到了命令行帮助文档里:

$ pipenv update --help
Usage: pipenv update [OPTIONS] [PACKAGES]...

类似下面的场景:

  • 用户:怎么运行程序呢?好,查下文档,文档里说「执行 run 命令就可以运行程序」。哎?怎么没用?
  • 开发者:哦,这个功能还没实现,先写出来让你练练手。

最终的结果就是,如果你想更新一个包,那就只能手动把更新版本的包版本和 hash 编辑到 Pipfile.lock 里。这么做实在是太蠢了。

卸载包

假设我决定不再使用 Gunicorn,需要卸载它,在文档里查到 pipenv uninstall 命令:

https://docs.pipenv.org/en/latest/basics/#pipenv-uninstall

于是执行下面的命令:

$ pipenv uninstall gunicorn

结果呢?为什么我所有的依赖又都被更新了!?好,我已经习惯了。看命令行帮助文档,同样有 --keep-outdated 命令:

$ pipenv uninstall --help
  ...
  --keep-outdated       Keep out-dated dependencies from being updated in
                        Pipfile.lock.  [env var: PIPENV_KEEP_OUTDATED]

再试一下,虽然我已经不抱期待了:

$ pipenv uninstall --keep-outdated gunicorn

顺便说一句,卸载的另一个问题是,当你卸载一个包的时候,只会卸载这个包本身,而这个包引入的相关依赖都会被保留,需要手动使用 clean 或 sync 命令修正(参考 Poetry README)。

不要使用 Pipenv(至少是现在)

当然,Pipenv 一直在改进。比如对 Windows 的支持,Lock 非常慢的问题,都有过很多的优化。

但是,种种证据都在表明,这其实是一个半成品。承诺了很多,兑现的却很少。或许过一段时间等它真正成熟了,能够保证基本使用流程,并且可以修改哪些反人类的设定以后再考虑用它(我怀疑这一条是否能实现,除非完全「去 Kenneth Reitz 化」,并且有一个核心维护者能够来推动执行)。

现在,请不要使用它。

我很抱歉在《Flask Web 开发实战》以及文章《Pipenv:新一代Python项目环境与依赖管理工具》中,推动更多人用它,给大家带来潜在的麻烦。我计划了一些补救措施,会逐一执行:

  • 写这篇文章【DONE】
  • 给《Flask Web 开发实战》的五个实例项目追加 requirements.txt 文件,并在 README 中添加说明。
  • 如果《Flask Web 开发实战》能出第二版,修改所有 Python 包安装命令,去掉所有 Pipenv 相关介绍。
  • 写文章介绍替代的几种工具和用法,包括原有的 virtualenv+pip、virtualenvwrapper、Poetry 等。
  • 在 PyCon China 2019 的闪电演讲《Python 虚拟环境和依赖管理工具大乱斗》里提及这个信息。
  • 以后写推荐文章时对这种带着强烈个人风格的项目保持警惕,并对 Kenneth Reitz 这个名字相关的东西保持警惕。

你可以选择用回 virtualenv+pip(+virtualenvwrapper),或是尝试新工具,我会在下一篇文章介绍主要替代品 Poetry 的基本用法。

(1)