您是否熟悉Python封装的历史? 您是否以数据包格式导航? 您是否知道即使看起来像奇迹-零依赖关系,也必须解开依赖关系的纠结? 我确信他们不像DepHell库的作者那样熟悉这一切。

我设法与
Nikita Voronov (通常被称为Gram或
orsinium)进行了交谈 ,并向他询问了有关未来报告的主题,不良的依赖性解决决定的痛苦,DepHell,pip,第一个比赛获胜原则,Guido,Pipfile,Python增量开发以及生态系统的未来。
-在Moscow Python Conf ++上,您将讨论依赖项及其旁边的所有内容。 您为什么只为报告选择这样的主题?因为这个问题贯穿了我使用Python的全部经验。 当我制作第一个程序包,编写第一个代码时,我考虑了如何帮助其他人以便他们可以安装它,并做了setup.py。 然后,他在一家公司工作,在另一家公司工作,在第三家公司工作,任务变得复杂而复杂。 起初只有一个requirements.txt文件,然后我意识到我需要修复依赖项,pip工具和一个锁定文件。 后来我们得到了Pipenv,然后是诗歌。
越来越多的问题逐渐暴露出来,我陷入了更加混乱的境地。 结果,我开始实现
DepHell ,这是一个用于管理依赖项的项目,该依赖项可以解析和读取其他格式的依赖项。 当我使用各种格式时,我已经看到了足够多的内容,现在我知道里面有多少内容,但是每天我都会学到一些新东西。 因此,我可以告诉您很多有关痛苦和错误决定的有趣的事情。
-痛苦总是很有趣。 您认为Python的这一部分现在有什么问题?JS有一个
node_modules
目录,每个依赖项都有自己的依赖项。 在Python中,情况并非如此。 例如,一个软件包安装在同一环境中,并且所有使用该软件包的软件包都使用该软件包的相同版本。 为此,您需要正确解决依赖关系-选择此程序包的版本,该版本通常可以满足此环境中的所有程序包。 这项任务非常艰巨:程序包彼此依赖,所有东西都交织在一起,解决依赖关系很困难。 Python实际上没有解析器。 聪明的解析器只在《诗歌》和《德pHell》中有。
pypi.org通常不提供有关程序包依赖项的信息,这使所有事情变得非常复杂,因为该信息必须由客户端指定,PyPI服务器无法自行解决。 因此,当PyPI说该软件包没有依赖性时,您将无法信任它。 您必须下载整个发行版,然后从setup.py解压缩并解析程序包依赖项。 这是一个漫长的过程,因此Python中的解析器不能很快。
Python中不仅有很少的解析器,而且在设计上也很慢。
在我的
报告中,我想说明DepHell解析的工作原理:如何构建依赖图,该图的外观,为什么大多数科学文章都依赖于如何解决依赖关系以及如何使用该图。 当然,有白皮书说明了这一切应该如何工作。 聪明的人用算法写过文章,但大多数情况下,它们不适用于Python。 因此,我将描述DepHell在实践中如何使用依赖项解析。
-我经常听到程序员说他们使用pip,并且一切对他们都很好。 他们在做什么错?他们很幸运,没有遇到依赖冲突。 尽管将两个包装放在干净的环境中时可能会出现问题。 最近发布了coverage 5.0软件包,如果您仅指定
pip install pytest-cov coveralls
,则pip会按顺序进行,并且第一个软件包将选择覆盖的最新版本,即5.0。
首个比赛获胜的原则适用于pip,因此,即使该版本与第二个软件包不兼容,第一个软件包也已被修复。 这种方法通常有效,但并非总是如此。
另外,存在可复制环境的问题。 由于pip始终放置最新版本,因此本地环境和生产环境中的版本可能会有所不同。 为了解决此问题,习惯上修复依赖性。 并且当相关性已经修复时,会指出pip应该安装的特定版本,然后pip已经可以正常工作。 Pip没有解析器,但是当其他人为它解析依赖项(例如DepHell或Poetry)时,它会提供解析器。
-为什么您认为这个主题现在如此重要? 为什么以前什么都没发生,但现在已经消失了,甚至朝着不同的方向走了?首先,Python生态系统正在发展。 有更多的软件包,必须安装更多的软件包,并且弹出更多的问题。 其次,关于文件格式的问题已经存在了很长时间,并且已经讨论了很长时间。
Setup.py通常是无法解析的,只能执行。 例如,如果我们想在Go中编写服务器以快速分发Python软件包,则我们不能仅仅获取并读取setup.py,因为它是一个可执行文件。 因此,为了执行它,您需要Python和完整的环境,并且通常还需要使整个项目都位于附近,并安装一些特定的依赖项。 除了所有这些困难之外,执行setup.py可能还会很危险,因为某些其他代码将在您的计算机上执行。 实际上,即使从当前用户那里执行代码也很可怕,因为例如,如果他收到了我的私人SSH密钥并将其发送到某个地方,那将是一个大悲剧。
定义依赖关系的第二个选项是Requirements.txt,它已经存在很久了,每个人都可以使用它。 同样几乎不可能以相同的方式进行解析。 Pip可以,但是这样做却非常非常困难:调用函数,迭代器的函数混合在一起。 而且,pip可以从requirements.txt中读取其一些键,例如,可以指定下载索引。 但这并不适用于所有键。
因此,要解析requirements.txt,您需要使用pip或某些第三方解决方案。 所有第三方解决方案本质上都是派生的,并且使用有关文件的某种假设。 并非pip可以读取的每个棘手的requirements.txt文件都可以读取这些fork。
Pip本身不打算用作库。 这是专用的CLI工具,只能从控制台使用。 所有pip源代码都隐藏在
_internal
后面,开发人员直接说:“不要使用它!”。 并且每个版本都破坏了向后兼容性。 老实说,它们不能保证兼容性,并且可以随时更改任何内容。 这就是发生的情况–每当有新版本的pip发行时,我都会从DepHell中损坏的CI中了解到这一点。
-用其他语言怎么样? 那里是否同样糟糕,还是所有这些问题都在某个地方得到解决?Guido van Rossum最近被授予Dijkstra奖。 我参加了他的讲座,并向他询问了Python依赖项。 圭多说,所有语言的成瘾都是混乱的,他努力不让自己陷入困境,并相信社区能够解决这个问题。
因此,在Python中,依赖项的工作由社区逐渐组织。 新的解决方案正在出现。 用Python构建Distutils之后,人们意识到附加的Setuptools会带来很多问题。
easy_Install
后来被开发来安装软件包,但是也有问题。 为了解决它们,创建了点子。 现在点子有很多问题。 它的来源在不断变化,没有架构,也没有接口。
社区正在尝试提出一些建议。 例如,关于需求2.0的问题进行了长时间的讨论,讨论如何使人们(这里是版本,这里是标记)以及其他语言以编程方式都能理解需求。
他们制作了一个Pipfile,但是由于pip非常令人困惑,因此他们无法为其添加Pipfile支持。
开发人员当然想这样做。 最有可能的是,有一天他们将能够使用,但到目前为止pip无法支持Pipfile。 因此,我们使pipenv可以与Pipfile和虚拟环境一起使用,并与该环境一起使用其他一些包装器。 但是,在pipenv中,所有的东西也混杂在一起并且混乱了。
对于其他语言,我喜欢Go中如何实现依赖项管理。 以前,其中没有版本控制,只有
go get
,您
go get
在其中指示从哪个存储库下载哪个软件包。 从初学者的角度来看,这很方便:您只需编写
go get
并且程序包已在系统中。 当您开始使用Python时,所有内容都会崩溃:某些版本,PyPI,pip,requirements.txt,setup.py,现在还包括Pipfile,Poetry,
__pymodules__
等。
随着Python在社区的帮助下逐步发展,遗产在生态系统中累积。 Go就是
go get
,但是又出现了一个问题,即需要修复依赖关系,以便尤其是环境可重现。
可以使用安装了所有依赖项的docker容器创建可播放的环境。 但是有时您需要更新各个依赖项。 例如,我们可能尚未准备好更新所有内容,因为该项目没有足够的测试来证明更新后一切仍然正常。 但是某些依赖关系可能需要更新,因为例如发现其中存在漏洞。 为此,最好不要有docker映像,而要有一个文件:“安装特定程序包的特定版本。”
Go中没有这样的东西,并且出现了供应商化:所有依赖项都被获取并放入一个目录中。 这是一个肮脏的解决方案,类似于
node_modules
,它在Go中已经使用第三方解决方案实现了一段时间。 在Python中,也使用这种方法,例如pip具有
vendor
目录。 当您安装pip时,没有建立依赖关系,您可能会认为一切都很酷,根本没有依赖关系,但实际上它们都在
vendor
内部。
大约一年前,go.mod(Go模块)出现在Go中。 这是一个新的内置工具,但也支持
go get
。 该项目包含两个文件:
- 一个描述项目直接与之相关的依赖关系;
- 另一个是锁定文件,它绝对描述了所有依赖项及其特定版本。
这是一个很酷的集中式解决方案。
重要的是,他们坚持认为某些事物应该以某种方式看待。 例如,在Go中,版本应为语义版本。
Python还提供了有关版本外观的规范。 为此,有PEP440。但是,首先,规范非常复杂:不仅有三个版本组件(编号),而且还有预发行版,后发行版和时代(版本更改方式时)。 其次,PEP 440并没有立即被接受,它们也逐渐被采用,因此支持旧版本,这意味着任何东西都可以用作该版本-像“ Hello world!”这样的任何行。
-您说社区已经逐渐开发该语言,因此有很多解决方案。 但是,为什么不清除所有这些垃圾呢? 为什么不扔掉Distutils,放弃没有人使用的旧的不必要的东西,而积极引入新的做法和工具呢?维护所有这些都是有意义的,因此您仍然可以安装旧软件包。 不可能坚持认为有必要这样做,而没有其他必要,因为决定是由社区做出的。 没有一个Core Python开发人员会说:“就是这样,我们现在正在做所有事情,没有钉子。”
Go具有立即使用依赖项所需的一切。 在Python中,您需要从外部重新安装所有内容,并且仍然需要了解确切的内容。 通常,点子就足够了,但是现在出现了其他选择。
在网站上,Python包装局(提供pip,pipenv,PyPI的小组)提供了正式的包装建议,并编写为使用pipenv。 与pipenv是另一个故事。 首先,它的分辨率很差。 其次,很长一段时间都没有发布,社区已经在等待创作者诚实地承认这个项目已经死了。 pipenv的第三个问题是它仅适用于项目,而不适用于包:您可以在pipenv中指定项目的依赖项,但不能指定其名称,版本,因此,将其放在包中以在PyPI上下载。 事实证明,遵循Python Packaging Authority的建议并使用pipenv仍然不足以解决问题。
诗歌试图成为革命。 它基本上不会生成setup.py文件,该文件对于向后兼容很有用,因为Poetry希望成为一切的新且唯一格式。 他知道如何收集软件包,并且拥有一个锁定文件,这是项目所需要的。 但是,诗歌有很多奇怪的东西,许多熟悉的功能不受支持。
-就依赖关系而言,您认为生态系统的未来是什么? 您的预测。一切都或多或少变得更好。 例如,我看到了一个点的空缺,并且向开发人员整理了一点空缺,并为此许诺了很多钱。 也许点子将成为更通用的解决方案。 但是您需要有人认真对待它:说我们正在这样做,现在我们正在遵循一些更严格的PEP,并将坚持遵守它(因为PEP只是建议,实际上没有人不需要遵循)。
例如,我们有这样一个故事:PyYAML的某个版本被锁定在锁定文件中。 有一天,通过CI的测试通过了,我们部署到了生产环境,一切都落在了那里,因为未找到PyYAML版本。 问题是锁定版本已从pypi.org中删除。 每个人都很愤慨,更新了锁文件,以某种方式幸存下来,但沉积物仍然存在。
不久之前,PEP 592出现了;它已经被采用并以点子形式维护,其中出现了被释放的释放。 Yank意味着该发行版尚未从pypi.org中完全删除-它是隐藏的。 也就是说,例如,如果您指定需要的PyYAML版本大于3.0,则pip将跳过被废弃的发行版并安装最新的可用版本。 但是,如果在锁定文件中指定了特定版本,并且此版本为yank,则pip仍将安装它。 因此,锁定文件和部署不会中断,但是,如果可能,将不使用旧版本。
第二个有趣的事情是
__pymodules__
PEP。 这些是轻量级的虚拟环境:打开项目目录,编写
pip install
PyYAML,并且PyYAML不是全局安装的,而是安装在
__pymodules__
目录中。 当Python在此目录中启动时,它不是全局导入PyYAML,而是从该目录导入。
我至少称其为虚拟环境,因为隔离度较低。 例如,无法访问二进制文件。 激活安装了pytest的虚拟环境后,可以从控制台使用它:只需编写pytest并执行某些操作即可。 使用
__pymodules__
将可以导入,但不能导入二进制文件,因为它们实际上并未安装。
此PEP旨在使初学者更容易。 这样一来,他们就无需处理复杂的虚拟环境,而只需通过pip install将所需的一切安装在
__pymodules__
中
__pymodules__
。
-好吧,您预测中的未来比现在更光明。是的,但是正如我说的,如果没有人说我们正在重做并试图扔掉遗产,那么问题仍然存在。 现在我们正在积累和积累工具,在不久的将来将不可能完全摆脱其中的任何工具。
-您怎么想,为什么没有开发人员可以更新依赖关系?几乎无论在公司还是在开源中,几乎没有任何地方已经建立了使用安全版本,原则上使用新的次要或主要版本的过程。 您在哪里看到问题?至少,当您要更新依赖项时,更新所有依赖项很令人恐惧,因为即使您通过了测试也不是万事大吉。 例如,Celery通常会出现这种情况,因为无法在测试中对Celery进行完全测试。 您可以锁定某些内容,简化某些内容,但是无法验证工作人员正在运行的事实。
与测试一起使用可以很好地实现,即使在Go Modules教程中,它也写了如何更新依赖关系:您可以更新某些依赖关系并运行测试。 而且,这些测试不仅要在您自己身上运行,而且还要依赖于此。
一个有趣的方面仍然值得一提:测试应该放在Python的软件包中吗? 从pypi.org下载软件包时,应该进行测试吗? 从理论上讲,它们应该甚至具有运行它们的机制:在setup.py中,您可以指定如何运行测试,它们具有哪些依赖关系。
但是,首先,许多人不知道如何运行它们,也不运行依赖的测试。 因此,通常不需要它们。 其次,这些测试通常具有非常困难的固定装置,因此将测试包括在包装中意味着包装要大6到10倍。
能够下载有测试但没有测试的软件包将是很棒的。 但是现在没有这种可能性了,因此测试通常不会在软件包内部加起来。 混乱不堪,我什至不知道在更新依赖项时是否可以对这些依赖项进行测试。
这方面似乎大部分都被忽略了。 但是在某些其他语言中,尤其是Go,被认为是一种好习惯,可以在环境中更新程序包,并立即对其进行运行测试,以确保该程序包在此环境中可以正常工作。
-为什么在Python中,用于自动语义版本控制的工具不受欢迎?我认为问题之一是该版本可以在很多地方进行描述。 最常见的是三种:项目本身和文档中的项目元数据描述格式(pypi.org,poety,setup.py等)。 在三个地方升级版本不是很困难,但很容易忘记。
DepHell有一个团队进行版本升级。 DepHell , , . semantic version, compatible version ..
, , .
Flit. Flit — , . :
init
,
build
,
publish
install
. , , PyPI — . Flit , . docstring . , .
DepHell Flit . description, , , .
, .
DepHell
import
, , , , . , , .
, Moscow Python Conf++ 27 . DepHell backend, web, , AI/ML, , DevOps, , IoT, infosec . , , Moscow Python Conf++.