`pip`的`package.json`和`package-lock.json`等价物是什么?

41
JavaScript的软件包管理器,例如npmyarn,使用package.json指定“顶层”依赖项,并创建一个lock-file以跟踪安装的所有软件包的特定版本(即顶层和子级依赖项)。

此外,package.json允许我们区分不同类型的顶层依赖项,例如productiondevelopment

对于Python,我们有pip。我想pip等效于lock-file的结果将是pip freeze > requirements.txt

但是,如果您只维护这个单一的requirements.txt文件,很难区分顶层和子级依赖项(您需要使用例如pipdeptree -r来找出这些)。如果您想删除或更改顶层依赖项,这可能会非常麻烦,因为很容易留下孤立的软件包(据我所知,当您pip uninstall一个软件包时,pip不会删除子依赖项)。

现在,我想知道:处理这些requirements文件的不同类型并区分顶级和子级依赖关系是否有一些公约?例如,我可以想象有一个requirements-prod.txt,其中仅包含生产环境的顶级要求,作为package.json的(简化)等效物,并且有一个requirements-prod.lock,其中包含pip freeze的输出,并充当我的锁定文件。此外,我可以为开发依赖项等拥有一个requirements-dev.txt等等。我想知道这是否是正确的方法,或者是否有更好的方法。附言:对于conda的environment.yml也可以提出相同的问题。

另请参见https://dev59.com/oFMH5IYBdhLWcg3wzzAe - djvg
2个回答

41

今天至少有三种好的选择:

  1. Poetry 使用 pyproject.tomlpoetry.lock 文件,类似于 JavaScript 世界中的 package.json 和锁定文件。

    这是我现在首选的解决方案。

  2. Pipenv 使用PipfilePipfile.lock,也像您描述的那样在 JavaScript 中使用文件。

Poetry 和 Pipenv 不仅仅用于依赖项管理。它们还可以开箱即用地为项目创建和维护虚拟环境。

  1. pip-tools 提供了 pip-compilepip-sync 命令。在这里,requirements.in 列出了您的直接依赖项,通常具有宽松的版本约束条件,并且 pip-compile 从您的 .in 文件生成锁定的 requirements.txt 文件。

    这曾经是我的首选解决方案。它向后兼容(生成的requirements.txt可以由pip处理),pip-sync工具确保虚拟环境与锁定版本完全匹配,删除不在您的“锁定”文件中的内容。


2
感谢您提供的出色答案,它指引我阅读了这篇有趣的文章。然而,我还是有些犹豫是否采用 pipenv,因为它使用 virtualenv 而不是 conda,而我非常喜欢(并依赖)conda 管理 Python 版本的能力。 - djvg
这是我个人认为支持pip-tools的另一个优点。它不会试图为您做太多事情。 - Chris
而且pip-tools也会处理“容易留下孤立的软件包”的问题,因为它会删除不在要求文件中的任何内容。 - Chris
好的,我会看一下。不过这会引入另一个依赖项。;-) - djvg
在这里插入一个+1来支持pip-tools,我喜欢它的轻量级和与其他任何工具的兼容性,因为它的输出只是一个普通的requirements.txt。 - Nick Crews
显示剩余3条评论

1
我有同样的问题,并提出了一个更通用和简单的解决方案。我使用众所周知的 requirements.txt 来列出所有明确的依赖项,而使用 requirements.lock 则列出所有包括子依赖项在内的软件包列表。
个人喜欢通过发行版内置的软件包管理器来管理 pythonpipsetuptools,并在 虚拟环境 中安装 pip 依赖项。
通常情况下,您会开始安装所有直接需要的依赖项。这将同时拉取所有子依赖项。如果您没有使用虚拟环境,请务必添加 --user 标志。
# If you already have a requirements file
pip3 install -r requirements.txt

# If you start from scratch
pip3 install <package>

如果您想升级软件包,您也有多个选项。由于我正在使用虚拟环境,所以我会始终更新所有软件包。但是您可以选择仅更新直接要求的软件包。如果它们需要更新其依赖项,则这些依赖项也将被拉入,其他所有内容将保持不变。
# Update all outdated packages (excluding pip and setuptools itself)
pip3 install -r <(pip3 list --outdated --format freeze --exclude pip setuptools | cut -d '=' -f1) --upgrade

# Update explicitly installed packages, update sub dependencies only if required.
pip3 install -r <(cut -d '=' -f1 requirements.txt) --upgrade

现在我们来到棘手的部分:保存我们的需求文件。确保先前的需求文件已经检入了git,这样如果出现任何问题,你就有一个备份。
请记住,我们希望区分明确安装的软件包(requirements.txt)和包含其依赖项的软件包(requirements.lock)。
如果您还没有设置requirements.txt,我建议运行以下命令。请注意,如果子依赖项已经被另一个软件包满足,它将不包括子依赖项。这意味着如果requests已经被其他软件包满足,它将不会出现在列表中。如果您的脚本明确依赖于这样的软件包,您可能仍然需要手动添加它。
pip3 list --not-required --format freeze --exclude pip --exclude setuptools > requirements.txt

如果您已经有一个 requirements.txt 文件,您可以使用 这个 sed 技巧 来更新它。这将保留所有子依赖项之外的内容,我们将在下一步中仅包含在 requirements.lock 中。

pip3 freeze -r requirements.txt | sed -n '/## The following requirements were added by pip freeze:/q;p' | sponge requirements.txt

最后,我们可以将所有依赖项输出到一个requirements.lock文件中,这将是我们所有软件包和版本的完整列表。如果我们在复现问题时遇到问题,我们可以随时回到这个锁定文件,并使用以前工作的依赖项运行我们的代码。

# It is important to use the -r option here, so pip will differenciate between directly required packages and dependencies.
pip3 freeze -r requirements.txt > requirements.lock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接