Python之美
首发于Python之美
也谈「不要用 Pipenv」

也谈「不要用 Pipenv」

前言

昨天有同学问我对 不要用 Pipenv 这篇文章的看法,看完觉得可以深入的再讨论一下,就有了此文。

在18年1月份我就曾经写过一篇 使用pipenv管理你的项目 推广pipenv。pipenv是17年初创建的,我一直关注Python社区各种前瞻的、新的东西,所以也关注它有一段时间了,但为什么在这个时间点才写文章推荐呢?其实就是因为一个对于pipenv的重大事件: 2017年12月7日PyPA官方接受了它( - 所以大家要多看推特消息)。其实PyPA在17年8月份就已经推荐使用pipenv了,但我认为官方和非官方差别太大了,一直保持观望态度。所以对我来说,「pipenv迁移到PyPA组织下」是一个非常明确的信号,这说明官方已经推荐使用它了(虽然不是唯一的)。

PS: 我这里说的「官方」是指PyPA(负责Python打包文件相关项目的组织)而不是Python官方哈,Python官方没必要对于打包工具表态。

另外,要明确下,官方推荐你用pipenv做的「应用依赖管理(Application dependency management)」。是的,包管理工具有多个,不是说有了pipenv就不推荐virtualenv、pip: 安装包目前最好的方法还是: virtualenv + pip。

对于「不要用 Pipenv」提到的观点,客观的说,我部分认同部分不认同,认同的是"不要在用Pipenv"和它的体验,不认同的是文中的一些观点。

不认同的部分

甚至借 PyPA 做背书来宣传(经常让人误以为是 Python 官方推荐)的工具却连基本的使用流程都没做好

作者Kenneth Reitz(以下简称KR)的特点是有idea,并且能把它实践出来。它做了很多有趣的、有用的东西,缺点是失去新鲜感后就喜欢烂尾~

KR在自己的博客里面说过pipenv的问题 - Pipenv: One Year Later & a Call for Help

具体的大家自己看,大意就是「问题和bug很多,欢迎大家来帮助」,另外在说社区接受项目时,说了句:

It’s been a lot of hard work — and very humbling to see how warmly (for the most part), the community has embraced the project.

我觉得作者还是知道自己的问题,不希望它烂尾所以给了PyPA。其他的几个有名的项目都是类似的做法:

  1. requests/requests-html 给了 Python Software Foundation
  2. python-guide 给了 Real Python
  3. responder 给了 taoufik07
  4. httpbin 给了 postmanlabs
  5. pep8.org 给了 dbader

所以作者并没有想着用来背书。

却连基本的使用流程都没做好这句话,我不认同,一会借着作者的Bluelog例子再深入。

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

早期pipenv的版本号还是传统的 Semver versioning(major.minor.patch),在 0.3.0 -> 0.3.1 时手误写成了 3.0.1。具体的可以看 HISTORY.rst,另外注意3.0.0是后来改的。往Pypi传过包的可能都误操作过,但后来发现传上去了就不能删掉这个版本了,非常痛苦,所以只能硬着头皮加版本号。而所谓的18.X.X是calver versioning(基于日历的版本),pipenv迁到pypa之后遵循了pypa组织的习惯,类似pip、sanic也是这样的用法。另外setuptools、virtualenv、attrs等项目也都是这种大major版本。

我认为版本号这个东西就是个标记,什么形式不重要,用多了就知道不是说0.X就不稳定,也不是major > 1就一定稳定。

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

整体过程我就不说了,总结几个作者的不爽:

  1. install「我安装一个包,默认行为竟然是更新其他所有不相干且已经锁定版本的依赖!」
  2. update时「All dependencies are now up-to-date!」
  3. uninstall时所有的依赖又都被更新了!?
  4. 使用「keep-outdated」和「--selective-upgrade」不生效(其实用了selective-upgrade就不用指keep-outdated了)还是不好使
Kenneth Reitz 先是说 lockfile 只要是过期了就总是会被重新生成

这是对的,Pipfile和Pipfile.lock是对应的,当执行pipenv install后改了Pipfile,对应的Pipfile.lock就一定会改。错误的是,不应该改那些不相关包的版本: 既然已经是==的了,就表明确定了具体版本呀。

这些问题,其实源于 Pipfile对应依赖在一开始没指定具体版本,也就是Pipfile对标requirements.txt,而Pipfile.lock只是当前环境的一个「快照」,如果Pipfile没有明确版本就用Pipfile.lock里面指定的。作者说的上述不爽,我也遇到过,我当时的做法是:

  1. 把确定要保证版本的库都在Pipfile里面定好版本,比如Flask,写成:
[packages]                                                                                                            
flask = "==1.0"
...
  1. 剩下的Pipfile.lock里面每次都变,我不关注 - 我反而更倾向于尽量有更新的包

对于这个例子,例如安装flask-avatars,我给一个方案:

import json
from pathlib import Path
import tomlkit

d = json.load(open('Pipfile.lock'))

p = Path('Pipfile')
document = tomlkit.loads(p.read_text())

for k, v in d['default'].items():
    document['packages'][k] = v['version']

p.write_text(tomlkit.dumps(document))

这样就把Pipfile.lock里面对应的包和版本反写到了Pipfile里面,这回再安装flask-avatars就不会把Pipfile.lock改乱了,大家可以试一下。这次Pipfile.lock修改的地方只剩下3种:

  1. flask-avatars有关的依赖
  2. 标有 markers的部分,这部分对Python版本很敏感
  3. 原来在Lock里面就用了类似`>=`这样不明确版本的写法,可以优化上面的脚本让这部分也确定下来。

PS: 其实KR在pipfile构想过和pip的集成,有兴趣的可以看一下。我觉得可以有一种方法快捷的把requirements.txt或者Pipfile.lock里面的内容写入Pipfile。

当然了,每次安装新的包,都可能有一堆依赖,需要每次都手动跑个脚本,把那些依赖都写到Pipfile里面,这个过程不是pipenv自己处理,很难受,这是我弃用的原因之一。

我认为目前「不要用 Pipenv」的理由

pipenv设计的很像npm或者yarn,但是实际上体验和yarn差的太远了。在今年4月发布Lyanna 2.3版本时,我就去掉了 用pipenv安装的相关内容。为什么呢?

1. 慢

先安装一遍看看时间(我已经用了代X理):

time pipenv install --dev --two
...
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 40/40 — 00:00:18  # 40个包
pipenv install --dev --two  43.25s user 20.74s system 108% cpu 58.877 total
❯ time pipenv install flask-avatars
...
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 29/29 — 00:00:05  # 29个包
pipenv install flask-avatars  54.42s user 17.18s system 90% cpu 1:18.86 total

这是我弃用最主要的原因。这个慢主要包含2部分:

  1. Lock慢。各位使用过就会有感觉吧?看看项目里面的issue,现在已经有154个issue包含slow这个词,随便点进去一个issue都可以看到很多人在反馈。我没有特别关注这个项目,想必在设计上是有问题的。其实在17年项目初期就有这个问题,我写文章时候试了下已经比之前的好太多了,原来Lock2分钟非常普遍。说回来,bluelog这个项目依赖算是比较典型,看上面: 初始化花了43s,安装flask-avatars花了54秒,但是注意,flask-avatars只依赖pillow和Flask(之前已经安装了),为啥花了这么久? 而且显示要安装29个包.... 凭什么... 所以一直到今日,这效果嘛,和yarn比还是差的不是一点半点: 我的直觉是它应该重写!
  2. 不缓存。重复执行install/update/lock等命令,完成时间没有明显提升。我们再安装两次flask-avatars:
time pipenv install flask-avatars
...
pipenv install flask-avatars  17.62s user 6.06s system 195% cpu 12.112 total
❯ time pipenv install flask-avatars
...
pipenv install flask-avatars  19.37s user 6.59s system 145% cpu 17.892 total

都约20s.... 根本不缓存,我不能忍。

PS:这里还有个技巧,如果确认不需要修改Lock文件,安装时可以跳过:

time pipenv install --skip-lock flask-avatars
...
  🐍   ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 14/14 — 00:00:05  # 14个包
pipenv install --skip-lock flask-avatars  11.52s user 3.16s system 180% cpu 8.136 total

这样时间快一些,因为要管理的依赖少了一半

2. 对项目发展没有信心

我对它没有信心不在于有大量的Issue和PR没有被处理,很多健康的项目都有这个问题。问题是:

  1. 上一次发版在2018.11.26,10个月了,不release个新版本是什么鬼?我深度怀疑现在属于烂尾状态
  2. 没有明确的发展方向。我看不到它准备新增、改进什么功能,看看最近的那些commit,更多的是改BUG,没有对现有问题安排计划解决,也没有看到有核心开发者在准备搞个大动作等等....
  3. BDFL。大家一看这个是不是以为我要说GvR?其实kennethreitz给自己安排了这么一个角色。也就是即便在PyPA组织下,PEEP(Pipenv的增强方案)都需要他的同意,而且在多个issue里面他都对PyPA的开发者们的意见提出过反对。很多项目都有这个问题:设想你好心想帮助添加或者改进什么,对方说我不喜欢,那么你对这个项目还有兴趣贡献自己的力量甚至能一而再再而三的尝试去说服对方嘛?反正要是我,肯定就算了。

唉,我当时给大家推荐时它还一片光明。技术是需要有前瞻性的,但是不是每个新事物最后都能坚持或者活下去。

用Poetry?

目前我觉得对依赖管理确实没有一个足够好的工具,我用回了virtualenv + pip。有同学会提到Poetry(说conda这种),poetry是一个更像yarn的解决方案,以前我也试用过。我现在的观点还是观望,理由如下:

1. 它分析依赖的时间非常的长。

用过的同学都体验过吧?这个耗时甚至远超pipenv!!这点来说,我觉得它的设计就有问题,可以说很傻...(写这篇文章时体验了一下,还是这样):

安装个包7分钟,这... 谁能忍?你们试试把bluelog项目的依赖用poetry add加一遍需要多久?我反正体验不下去了

--- UPDATE: 2019-09-03 07:58 ---

经大家反馈,我昨天在公司今天在家又试了下,很快。不知道发生了什么... 我记得之前试用时也是很慢... 那我找时间再深入学习一下它...

2. 没有任何官方和核心开发者支持

大家为什么不喜欢我当时没深入,就是用的时候觉得配置文件好麻烦,文档简陋。但是「哪天黄了就是真的黄了」这点,我还没有勇气在生产环境中用它。

3. 未处理的Issue/PR太多

虽然不是决定性的,但是对于这Star不到6K的项目来说我是不敢用的

编辑于 2019-09-03

文章被以下专栏收录