• Pipenv: 吹嘘自己无所不能,实际上没什么卵用
  • 发布于 2个月前
  • 792 热度
    0 评论
  • 石更生
  • 0 粉丝 8 篇博客
  •   

Pipenv是一个Python打包工具,它可以很好地完成一件事-应用程序依赖管理。然而,它也受到各种问题、局限性和发展进程的影响。过去,Pipenv的宣传材料在其目的和支持者方面具有很高的误导性。
在这篇文章中,我将探讨Pipenv的问题。它真的是Python.org所推荐的吗?每个人——或者至少是绝大多数人——都能从中受益吗?


 “官方推荐工具”,或者说我们怎么做到的
“Pipenv——是Python官方网站Python.org推荐的Python打包工具,免费(自由版)。”
在好几个月期间Pipenv的README文档中常常有上面那句口号:这句口号是2017-08-31加进去README文档里的,最终在2018-05-19被删除。有那么一阵子(在2018-05-16),有说明(管理应用程序依赖,还有Pypa而非Python.org),而在约15分钟的时间里,这句口号变成了“Pipenv是世界上最烂的”或其他大概是这个意思的话(这是维护者的吐槽)。
README这句口号声称Pipenv是Python打包工具的关键。问题是:不是这样的。Pipenv可能有时会有点用,但是对于其他许多人来说,试用这个工具只会让人沮丧。我们稍后会探讨这个问题。
这个口号值得吐槽的地方还有“Python.org”和“官方”这两点。让它变得“官方”的是有关Packaging.python.org的一个简短教程[1],这是Pypa的打包用户指南。还值得注意的是它引用了Python.org这个词,这听起来好像Pipenv得到了Python核心团队的认可。Pypa(Python Packaging Authority)是一个独立的组织——它们负责Python的打包文件(包括pypi.org、setuptools、pip、wheel、virtualenv等),所以这认可具有误导性。当然,Pypa是Python的一个很有价值的部分;核心团队的认可-比如,包含在官方Python发行版中-要重要得多。
这条标语引发了许多激烈的讨论,可能是自5月在Reddit网贴出来后最受热议最重大的帖子了。这种改变是由Reddit网的这个帖子引发的。我建议全文阅读这个帖子。

Pipenv能做什么
我们已经了解到Pipenv用于管理应用程序依赖。让我们来了解一下这个词的真正含义。

应用程序依赖
以下是Pipenv的一个示例:我正在开发一个基于Django的网站。我创建了~/git/website,并在该目录中运行pipenv install Django。Pipenv:
.自动在我的主目录中的某个位置创建一个虚拟环境。
.编写一个Pip文件,它将Django列为我的依赖
.使用pip安装Django
.继续编写Pipfile.lock文件,它存储每个已安装包(包括pytz,即Django的依赖)的确切版本和源文件碎片[2] 。
这个过程的最后一部分是最耗时的。在锁定依赖版本的同时,Pipenv暂停了46秒钟,这是Pipenv值得注意的问题之一:速度慢。当然,这不是唯一的一个,但它绝对没有帮助。损失46秒并不多,但是当我们在计时测试部分中进行更长的等待时,会知道这个小问题会让用户觉得使用这个工具包很麻烦。


运行脚本(差劲)
但我们还是继续吧。pipenv run django-admin startproject foobanizer 是我现在必须要用到的,它不便输入,并且芝麻绿豆的小事都要运行pipenv。(manage.py脚本框有/usr/bin/env python。) 我可以运行pipenv shell来获得一个新的壳,这个壳默认运行activate脚本,让您在虚拟激活中体验两者中最糟糕的一个:新壳的笨拙,和造壳支持者不喜欢的激活脚本。
使用pipenv shell意味着生成一个新的子壳,执行壳启动脚本(例如bashrc),并要求您使用Exit或^D退出,如果您键入 “ deactivate”,则在虚拟程序之外使用一个额外的壳。或者你可以使用—fancy模式在启动子shell之前操纵$PATH,但是它需要一个特定的壳配置,其中$PATH在非登录壳中不被覆盖-还经常更改终端模拟器的配置以运行登录壳,因为许多Linux终端不这样做。
为什么会发生这种事?因为一个命令不能操作它生成的壳的环境。这意味着Pipenv必须假装它所做的是一件合理的事情,而不是一个解决办法。这可以通过使用source$(pipenv-venv)/bin/activate(可以变成一个整洁的别名)或壳包装器(类似于虚拟包装器)来手动激活解决。


都完成了
不管怎样,我想在我的网站上有个博客。我想用Markdown语法编写它们,所以我运行pipenv install markdown,几秒钟后,它被添加到两个Pipfile中。我可以做的另一件事是pipenv install-dev IPython,并获得一个方便的壳进行修改,但它将被标记为开发依赖——因此,不会安装在生产中。最后一部分是使用Pipenv的一个重要优势。
当我完成我的网站工作后,我将两个Pip文件提交到我的git存储库中,并将其推送到远程服务器。然后我可以克隆到,比如说, /srv/website。现在,我只需运行pipenv install就可以安装所有的生产包(但不安装开发包-Django、pytz、Markdown将被安装,但IPython及其所有的所以依赖项都不会安装)。只有一个警告:默认情况下,仍将在当前用户的主目录中创建虚拟服务器。在本例中,这是一个问题,因为它需要由nginx和uWSGi访问,它们无法访问我的(或根目录)主目录,也没有自己的主目录。这可以通过导出PIPENV_VENV_IN_project=1来解决。但是请注意,每当我通过Pipenv使用/srv中的应用程序时,我都需要导出这个环境变量。该工具支持加载.env文件,但只有当运行pipenv shell和pipenv run时。你不能用它来配置Pipenv。要使用nginx或uWSGI运行我的应用程序,我需要知道精确的虚拟路径,因为我不能将pipenv run作为uWSGI配置的一部分。


Pipenv不能做什么
上面提到的工作流程看起来很合理,对吧?有一些不足,但除此之外,它似乎工作得很好。Pipenv的主要问题是:它在一个工作环境下运行,并且只能在一个工作环境下运行。尝试做任何其他事情,会有很多麻烦。
 
Setup.py,资源分配,还有wheels文件
Pipenv只关心管理依赖关系。这不是一个包装工具。如果你想把你的东西放在PyPI上,Pipenv派不上什么用场。您仍然需要使用install_Requires编写setup.py,因为Pipfile格式只指定依赖和运行时需求(Python版本),因此包名没有位置,Pipenv不强制/期望您安装项目。管理开发环境(作为Requiments.txt替换,或用于编写上述文件的东西)时比较方便,但是如果您的项目有setup.py文件,则仍然需要手动管理install_Requires。Pipenv也不能自己创造wheels文件,而pip freeze 将会比Pipenv更快。


在项目驻点之外运作
Pipenv的另一个问题是使用工作目录来选择虚拟环境。[3]假设我是图书馆的作者。我的foobar库的一个用户刚刚报告了一个bug,并附上了一个repro.py文件让我重现这个问题。我将该文件下载到我的文件系统上的~/Download目录。用普通的旧虚拟器可以很容易地用以下语句在备用外壳中重现:

$ ~/virtualenvs/foobar/bin/python ~/Downloads/repro.py


然后我可以启动我的高级IDE来修复这个bug。我不需要在这个项目中cd。但对于Pipenv,我真的不能这么做。如果我用命令行选项在.venv中加上虚拟,就可以键入~/git/foobar/.venv/bin/python~/Download/Replei.py。如果我使用集中式目录+碎片,且还没有储存碎片,那么Tab实现就变得强制性了。

$ cd ~/git/foobar
$ pipenv run python ~/Downloads/repro.py


如果我有两个.py文件,或者reple.py文件,否则就依赖于在当前的工作目录中,该怎么办?

$ cd ~/git/foobar
$ pipenv shell
(foobar-Mwd1l2m9)$ cd ~/Downloads
(foobar-Mwd1l2m9)$ python repro.py
(foobar-Mwd1l2m9)$ exit  # (not deactivate!)


这样相当不好看。而且,使用虚拟包装器,我可以这样做:

$ cd ~/Downloads
$ workon foobar
(foobar)$ python repro.py
(foobar)$ deactivate


别忘了Pipenv帮不了我编写setup.py文件、分发代码或管理释放。它只是管理依赖,而且做得很糟糕。

Nikola
我是一个静态站点生成器的副维护者:Nikola。作为其中的一部分,我需要在以下地方运行Nikola:
~/git/nikola
~/git/nikola-site
~/git/nikola-plugins
~/git/nikola-themes
~/website (此博客)
/Volumes/RAMDisk/n (演示站点,用于测试,并在需要时在RAM磁盘上创建)
列表很长。Nikola的最终用户可能没有那么长的列表,但他们可能有不止一个Nikola站点。对我和上述用户来说,Pipenv不起作用。要使用Pipenv,所有这些存储库都需要在一个目录中。我还需要为Nikola用户创建一个独立的Pipenv环境,因为这需要Django。此外,如果我们要利用项目中的文件,则Pip文件必须与~/git/Nikola连接。所以,为了让Pipenv变得巧妙,在SSD上进行测试/bug复制(并且更快地磨损它)等…我想有一个~/Nikola目录。我也可以直接使用虚拟环境。但是在这种情况下,Pipenv失去了它的用处,使我的工作流程更加复杂。我不能使用虚拟包装器,因为我需要黑掉一个模糊匹配系统到它上,或者记住附加在我的虚拟名称上的随机字符串。这都是因为Pipenv过于依赖当前目录。
希望使用Pipenv的Nikola最终用户也将强制使用特定的目录结构。如果该站点充当项目的文档,并且在另一个项目中复制怎么办?两个虚拟环境,浪费掉100兆字节。或者更糟的是,Nikola最终进入了另一个项目的Pipfile,这在技术上对我们的下载统计数据是好的,但对其他项目的贡献者却不是很好。


我试着测时间的那一部分
Pipenv以速度慢而闻名。但它到底有多慢?我对它进行了测试。我使用了两个测试环境:
远程:DigitalMarine VPS,这是最低廉的(1 VCPU),Python 3.6/Fedora 28,在法兰克福
本地:我的2015年买的13英寸苹果笔记本电脑上(基本模型)Python 3.7,在一个相当慢的互联网连接上(好的时候网速达1000万bps,但一个测试都没执行到)
两者都是在2018年7月1日运行由pip安装的Pipenv。
同时也安装了以下cache:
1.移除的: ~/.cache/pipenv 被移除
2.部分: rm -rf ~/.cache/pipenv/depcache-py*.json ~/.cache/pipenv/hash-cache/
3.不变的: 跟上一次运行没什么变化
事实证明Pipenv喜欢用缓存和锁定来做一些奇怪的事情。查看活动显示器发现,当Pipenv显示其锁定[包]依赖项时,网络活动正在进行…线程挂起来。没有文件会告诉你。最糟糕的例子是在两次运行中完成的本地Nikola安装:第一次运行Pipenv InstallNikola在安装软件包后立即中断[4],因此缓存中有所有必要的wheels文件。安装花费了10分7秒,其中9分50秒是由锁定依赖项和安装锁定的依赖项完成的-因此,大约有9分钟半时间盯着静态屏幕,工具在后台做一些事情-Pipenv不会告诉您这个阶段会发生什么。
(更新于2018-07-22:在Pipenv测量中:第一项是Pipenv可执行的总时间。第二项是等待Pipenv完成其“主要”任务:锁定依赖项并安装它们。计时在Pipenv开始锁定依赖项时开始,在提示符出现时结束。第三项是Pipenv报告的安装时间。因此,Pipenv安装时间⊇锁定/安装时间⊇Pipfile.lock安装时间。)

替代工具和新工具
Python打包似乎没人会满意。因此,有许多新的竞争者作为“最好的新包装工具”的角色。除了Pipenv之外,还有Hatch工具(由Ofek Lev创作)和Poetry工具(由Sébastien Eustace创作)。两者都被列为“正式”教程中的备胎选项。

Hatch¶工具
Hatch工具试图管理整个打包过程。它可以说是一种资产,因为它有助于替代其他工具。然而,也可以说,它增加了一个单一的失败点。Hatch工具根据标准的文件例如requments.txt和setup.py来工作,因此很容易被其他文件替换。它不像Pipenv那样搞那么多花样,而是更容易配置。Hatch工具所做的一些选择是可疑的(例如手动解析pkg/_init_.py以获取版本号,将测试套件安装到Site-Package(相当常见的疏忽),或者它的壳特性与Pipenv一样难看),而且它不做任何管理依赖的工作。它不一定适用于我前面提到的Django用例,也不一定适用于软件的最终用户。


Poetry¶工具
Poetry工具介于两者之间。它的主要目标是接近Pipenv,但也可能分配工作给PyPI。它极力隐藏它在幕后使用Pip的事实。它的自述文件附带了一个“关于Pipenv”的延伸阅读部分,我建议阅读——它有更多Pipenv的不好的特性。Poetry工具声称使用标准化的(PEP 518)pyproject.toml文件来代替通常的大量文件。不幸的是,唯一标准化的是文件名和语法。Poetry工具使用自定义[tool.poetry]部分,这意味着人们需要Poetry工具充分使用它创建的包,这样就离不开供应商了。(上述Hatch工具还生成一个pyproject.tmpl,其中包含元数据部分…)这里有一个build特性来生成一个带有setup.py以及关联文件的sdist。
在一个简单的Poetry工具加Nikola测试,花了24.4s/15.1 s/15.3 s来修复依赖(根据Poetry工具的计数,远程环境,缓存移除),完成了放心输出,没有无声无息的锁定。比不上pip,但比pipenv更好。此外,代码库和布局是相当复杂的。Poetry工具产生工具包而不仅仅是管理依赖,所以它通常比Pipenv更有用。


Pip要留在这里了!
但是在所有新工具的讨论中,我们忘记了旧工具,它们做得很好-事实上,新工具仍然需要它们。
Pip做事又快又好。它缺乏分割生产包和开发包(如Pipenv和Poetry工具)的支持。这意味着Pip冻结和pip安装是即时的,代价是:(A)需要两个独立的环境,或者(B)在生产中安装开发依赖项(这应该是对HDD空间的浪费,而在架构良好的系统中仅此而已)。
虚拟环境管理特性可以由虚拟包装器提供。该工具的主要优点是壳脚本实现,这意味着WorkonFoo不需要生成新的子壳就激活Foo虚拟程序(Pipenv、Hatch工具和Poetry工具中的一个问题,我在描述Pipenv操作时在“运行脚本(差劲)”一章中提过的)。Pipenv拥护者经常提出的一个论点是,创建虚拟环境无需关注自身,也不需要关心它在哪里。不幸的是,许多工具都需要用户拥有这种知识,或者强制指定一个特定的位置,或者要求它与主目录不同。
对于一个具有自动发布的合理的项目模板,我有自己的叫法,称为(伪原创的)Python项目模板(PyPT)。
是的,setup.py文件并不理想,因为它们使用.py代码和函数执行,使得很难访问元信息(./setup.py egg_info创建可访问工具的文本文件)。它们的主要优点是它们是唯一被广泛支持的格式——pip实际上是默认的Python包管理器(在windows和mac上预装),其他工具需要先安装/引导。


Pipenv的夭折速度
一个好的包装工具是稳定的。换句话说,它不常变,它努力维持现有的环境。重新下载系统上的所有内容一点都不好玩,”/usr”现在被改成”/stuff”,“/usr”中的所有文件都用不了而且删除不了。这就是Pipenv的杰作:

在一个月的时间里,虚拟主机的位置发生了两次变化。如果用户没有读取Changelog,也没有手动干预(同样值得注意的是,在问题中和v3.3.4的Changelog中都提到了选项名),那么他们就会有一个旧.venv目录,因为他们采用了新的方案。然后,在切换到v3.5.0之后,它们将在主目录中隐藏一个陈旧的虚拟主机,因为pipenv决定添加碎片。
而且,这是不可配置的。即使用户想要禁用路径中的碎片,也不能禁用。很适合那些想要混合Pipenv和虚拟包装器的人。
Pipenv是一个非常固执己见的工具,如果开发团队改变主意,旧的方法就不被支持了。
Pipenv更新速度很快,并且很不负责任。例如,2018-03-13:21和2018-03-14 13:44(略超过24小时)期间,Pipenv更新了10次,从11.6.2到11.7.3版本不等,完全没通知用户在每个版本中改变了什么。
延伸阅读:
Kenneth Reitz,致/r/python的一封信(附一些例子解释下极度混乱的情况)
Reddit新闻网关于这封信的评论
 
结论
与流行的观点相反,Pipenv并不是Python.org官方推荐的工具。它只是在Packaging.python.org(Pypa运行的页面)上写了一个关于它的教程。
0.Pipenv相当好地解决了一个用例,但在其他许多用例中失败了,因为它强制用户执行特定的工作。
1.Pipenv不处理任何打包过程(不能产生sidst和wheels文件)。希望上传到PyPI的用户需要手动管理setup.py文件,用不着Pipenv。
2.Pipenv生产锁文件,这对复制很有用,但安装速度较慢。速度是一个值得注意的问题,pip freeze足够好了,即使没有依赖类(生产和开发),也没有碎片(这有小好处)[2]。
3.Hatch工具试图替换许多打包工具,但一些实践和想法是可疑的。
4.Poetry工具支持跟Pipenv一样的缺口,同时也增加了创造包的能力,并改善了pipenv的许多不好的地方。一个值得注意的问题是使用自定义的无所不包的文件格式,这使得切换工具更加困难(供应商锁定)。PIP、setup.py和virtualenv——传统的、用过的真正的工具——仍然可用,并在不断地进行开发。使用它们可以带来更简单、更好的体验。同样值得注意的是,像虚拟包装器这样的工具可以比前面提到的新Python工具更好地管理Virtualenv,因为它基于壳脚本(它可以修改环境)。
[1]在附带说明中,教程什么都没解释。可能有人知道它类似于NPM或Bundler(即什么?),安装一个包,并通过pipenv运行.py文件。
[2](1,2)请注意,上传后不能更改PyPI上的文件,因此这只会防止流氓PyPI管理或MitM攻击(遇到攻击会更麻烦)。而且,这个特点相当不好。
[3]幸运的是,它还查找Pip文件的父目录。否则,您可能会得到foo的一个环境,foo/foo的另一个环境,foo/docs的另一个环境,等等,…
[4]由于内存磁盘空间不足而错误中断,其实是件好事。

用户评论