我应该固定我的 Python 依赖版本吗?

Should I pin my Python dependencies versions?

我即将发布一个 Python 库,我在过去几周一直在努力。我已经阅读了很多关于 Python 依赖项的内容,但还不太清楚:

有些人假装你应该永远不要固定你的依赖版本,因为它会阻止你的库的用户升级这些依赖。

其他一些人声称您应该始终固定您的依赖项版本,因为这是保证您的版本按照您开发时的方式工作并防止这种情况的唯一方法依赖项中的重大更改会对您的库造成严重破坏。

我以某种方式寻求混合解决方案,假设我的依赖项使用 semantic versioning 并仅固定主版本号(例如 somelib >= 2.3.0, < 3),除非主版本号为 0 ](语义版本控制规定此类版本被认为是易变的并且可能会破坏API,即使只有补丁号被撞)。

截至目前,我不确定哪种方式最好。是否有官方指南(甚至可能是 PEP?)规定有关 Python 依赖项的最佳实践以及如何指定它们?

您应该始终固定您的依赖项,因为它增加了安全、可重复构建的可能性,即使随着时间的推移也是如此。固定版本是您作为包维护者的声明,您已验证您的代码在给定环境中工作。这有一个很好的副作用,可以保持你的理智,因为你不会被错误报告淹没,在这些错误报告中你必须扮演检查员的角色来检查每个包的相互依赖性和系统细节。

用户始终可以选择忽略固定的依赖版本,并自行承担风险。但是,当您发布新版本的库时,您应该更新依赖版本以进行改进和错误修复。

PEP 426 about Semantic dependencies 部分(Python 软件包的元数据)指出:

"Dependency management is heavily dependent on the version identification and specification scheme defined in PEP 440 (PEP 440 - Version Identification and Dependency Specification)."

据此,我推断权威 "best practice" 是对您的依赖项进行版本控制,因为 PEP 在打包方面的关系在相关 PEP 概述的版本控制细节上被声明为 "heavily dependent" .

固定可能会产生问题并导致安全风险。特别是对于一个库,就像你的情况一样,如果它通常与其他本身具有依赖性的 PyPI 包结合使用,它可能会导致 更多 依赖性冲突。

为什么? Python Dependency Resolution 的详细研究,在分析了数以万计的 PyPI 包及其当前的依赖冲突率之后,讨论了这个问题。它解释说:

if the distribution is not installed into its own, empty, single-purpose environment, then the likelihood of dependency conflicts is substantially increased if dependency versions are all pinned instead of leaving the ranges flexible.

并指出 固定会干扰升级,从而加剧安全问题

它建议:

If a project pins dependencies, then it must be prepared to issue a new release every time there is an important release of anything the project depends on directly or indirectly, all the way down the dependency chain.

其他两个答案相互矛盾的原因是它们都是正确的(并且值得一读),但它们适用于不同的情况。

如果您要在 PyPI 上发布一个库,您应该声明您知道的任何依赖项,但不要固定到特定版本。例如,如果您知道需要 >= 1.2,但 1.4 已损坏,那么您可以编写类似 somepkg >= 1.2, != 1.4 的内容。如果你知道的一件事是 somepkg 遵循 SemVer,那么你可以添加一个 < 2.

如果您正在构建自己部署的网络应用程序之类的东西,那么您应该固定所有确切的依赖项,并使用 pyup.io 或 requires.io 之类的服务来通知您新版本发布。这样您就可以保持最新状态,同时确保您部署的版本与您测试的版本相同。

请注意,这两条建议相互补充:如果应用程序 A 使用库 B,那么 A 的作者或 B 的作者都可以固定 B 的依赖项,但不能同时固定两者。所以我们必须选择一个。这里的基本原则是最好尽可能晚地完成,即由 A 的作者完成,他可以看到他们的整个系统;图书馆 B 的工作是传递一些有用的提示来帮助 A 做出这些决定。特别是,如果 A 所依赖的所有库都准确地记录了它们对底层依赖项的了解,那么当它们重叠时,A 就可以做出明智的决定。就像如果依赖项 B 依赖于 requests >= 1.0, != 1.2,而依赖项 C 依赖于 requests >= 1.1,那么我们可以猜测 1.1 或 1.3 可能是固定的好版本。如果依赖 B 依赖于 requests == 1.1 而依赖 C 依赖于 requests == 1.2,那么我们就卡住了。