瑞瑞哥的博客

setup.py和requirements.txt区别

setup.py和requirements.txt区别

这篇文章是一篇译文,翻译自https://caremad.io/posts/2013/07/setup-vs-requirement/。**翻译行为已经征求到原作者同意**,但是由于英文能力所限,翻译的不好,所以有能力的人还是请看原文。

另外,我注意到https://pyzh.readthedocs.io/en/latest/python-setup-dot-py-vs-requirements-dot-txt.html 也有原文的中文翻译,不过我翻译接近完成的时候才发现,但是还是把它翻译完了。

关于setup.pyrequirements.txt ,以及它们的职责,网上存在着很多误解。一大堆人觉得他们是重复的,甚至创造了一些工具,去处理这种重复的内容。

Python Libraries

在这篇文章中,一个Python 库 指的是一个被开发出来、被发布出来给人使用的东西。你可以在Pypi上找到一大堆Python 库。为了保证成功发布一个Python 库,需要提供一大堆元数据,比如名称、版本、依赖等等。这些元数据就写在setup.py里,比如像这样:

1
2
3
4
5
6
7
8
9
10
11
from setuptools import setup

setup(
name="MyLibrary",
version="1.0",
install_requires=[
"requests",
"bcrypt",
],
# ...
)

这个(范例)很简单,你申明了所需的元数据。但是这个范例中,没有指定我们应该从哪里获取依赖。上述文件只是申明了我们有requestsbcrypt这两个依赖,但是没有告诉我们应该去哪个网站,或者哪个机器上获取它们。我把这种依赖叫做“抽象依赖”。这种抽象依赖只有一个名字,最多再有一个可有可无的版本号。这种思想类似于Python编程思想中的”鸭子类型”,你不在乎你获得的requests是哪个,只要你获得的看起来像是requests

Python Applications

当我说到Python 应用,我指的是一个你可以用来部署的东西。它可能在Pypi上面,也可能不在,但它基本上没有太多的可复用性。一个存在于Pypi的应用,基本上需要一个跟部署有关的配置文件,这个文件用来处理那些部署方面的问题。

一个Python应用基本上都会有一些依赖,有时候甚至有一大堆依赖,这些依赖都是被测试过的。而一个已经部署好的Python应用,是不会有名称或者打包相关的元数据的。这些元数据都被写在pip requirements文件里了。一个典型的requirements文件可能会是这个样子:

1
2
3
4
5
6
# This is an implicit value, here for clarity
--index-url https://pypi.python.org/simple/

MyPackage==1.0
requests==1.2.0
bcrypt==1.0.2

这里你能够看到,每个依赖都有一个确切的版本号。一个Python库倾向于有一个大范围的、模糊的版本号,但是一个Python应用需要一个确切的版本号。你装了哪个版本的requests并不重要,但你需要在生产环境中安装同样的版本,因为你已经在本地测试过了。

在这个文件上面,你可能也会看到一个--index-url https://pypi.python.org/simple/。它是requirements.txt很重要的一个部分,这一行把抽象的依赖,(比如requests==1.2.0),转为确切的、来自 https://pypi.python.org/simple/“、而且版本号为1.2.0的依赖。这一次就不是鸭子类型了,这个类似isinstance()相等判断。

所以为什么抽象和确切很重要?

读到现在,你可能会说,ok我已经明白setup.py是为那些“可再发行的东西“设计的,requirements.txt是为那些“不可再发行的东西”设计的,但我已经有一些工具,它读取requirements.txt,然后把读到的依赖,填写到setup.py中的install_requires=[...]里面去。那么我为什么还要在乎这些东西呢?

抽象和确切的区分还是重要的。这样的话,我们可以使用那些Pypi的镜像站,一个公司也可以建立他们自己私有的包目录,甚至你可以fork一个库的分支,去修复一个bug或者增加一些新的特性。因为一个抽象的依赖只是一个名称和一个可有可无的版本号,所以你可以从Pypi或者一些别的地方安装它,比如Crate.io或者你自己的文件系统。你可以fork一个库,修改他的代码,只要它有正确的名字和版本号,Python库就会使用它。

当在一个本应该使用抽象依赖的地方,用了一个确切的依赖,会发生什么呢? Go 语言里面有一个更加极端的例子。在Go语言中,默认的包管理器 (go get) 允许你通过代码里的URL去指定你的imports,这些代码是包管理器收集和下载的。具体如下:

1
2
3
import (
"github.com/foo/bar"
)

这里你可以看到,这个URL指向了一个确切的依赖。现在如果我用了这个库,但是在它依赖的bar库中,有个bug影响我,或者我需要增加一个新特性,这时候我就需要修改这个名为“bar”的库。那么我不仅要fork那个名为bar的库,我还得fork那个依赖了bar的库(译者注:如果不修改依赖了bar的库,那么它还是用的有问题的版本)。更差劲的是,如果这个依赖是5层的,那么我可能会得fork5层中5个不同的库,仅仅为了让他们指向一个稍微有点不同的”bar”。

Setuptools 的一个缺陷

类似上面go语言的例子,setuptools也有一个相似的问题。这个问题叫 dependency links ,具体如下:

1
2
3
4
5
6
7
8
9
from setuptools import setup

setup(
# ...
dependency_links = [
"http://packages.example.com/snapshots/",
"http://example2.com/p/bar-1.0.tar.gz",
],
)

setuptools 的dependency_links参数将本应该是抽象的依赖,变成了具体的依赖了。这里所说的具体的依赖就是指上文代码里硬编码的Url。现在和go非常类似的是,如果我们想要修改一个包,我们必须深入代码,修改依赖链上的每个包,去修改dependency_links

译者注:我的理解是这样的:

1
2
3
4
5
如果python应用依赖了库C,C依赖了库B,B依赖了库A。而且都用了dependency_links这种具体的、写死的做法。
那么当库A发生一个问题的时候,我们只能自己改代码修复这个问题的时候,我们发布了A.bugfix版本,然后把它放到我们公司的代码仓库。
但是B里面,依赖的A还是社区的有问题版本,不是我们发布的A.bugfix版本,我们必须把B里面依赖A那句话也改掉。
于是我们修改了B里面的dependency_links,让B使用了公司内部代码仓库的A.bugfix版本,而这个版本的B,就叫做B.bugfix。
同理,你还得搞一个C.bugfix版本出来。

开发可复用的内容 or 如何不让自己重复做同一件事

应用的区别看起来没什么问题,但当你正在开发一个库的时候,某种意义上说它就是你的应用。在setup.py中,你知道你应该使用抽象的依赖;在 requirements.txt中,你知道你应该使用确切的依赖。但是你并不想维护两个独立的列表,否则它们两个难免会不同步。而requirements.txt可以帮我们处理这种情况。给定一个目录,如果目录下有setup.py文件,那么你就可以这么写你的requirements.txt文件:

1
2
3
--index-url https://pypi.python.org/simple/

-e .

现在命令pip install -r requirements.txt还是会和之前一样正常工作。它会先:

  1. 安装路径.上面的库(我理解这一步是什么都没做的,因为你当前路径一般不会放什么库)
  2. 然后找到setup.py文件里面的抽象依赖,再把抽象依赖和参数--index-url里指示的源相结合,变为具体的、确切的依赖,然后安装他们。

还有一个有用的功能。比如说你有2个或者2个以上的库,开发时候你是一起开发的,但是你想分开发布;或者你把一个库分割成了几个部分但是还没有正式发布它们。如果你顶层的库仍然只是按照名字来依赖(前面说的这些库)的话,当你用requirements.txt的时候,你可以安装开发版本;当你不用的时候,你可以安装发布版本。把文件这么写即可:

1
2
3
4
--index-url https://pypi.python.org/simple/

-e https://github.com/foo/bar.git#egg=bar
-e .

这将会:

  1. 先从 https://github.com/foo/bar.git安装名为`bar`的库,名字就是bar;
  2. 然后会安装本地的包,一样地,会把本地的抽象依赖和--index-url里面指示的源相结合,结合成确切、具体的依赖再安装。

不过第二步的时候,因为bar已经在第一步的时候安装过了,所以这次会跳过,然后继续用开发版本的bar

参考

https://docs.python.org/2/library/os.html

https://blog.csdn.net/u013061183/article/details/78525807

https://www.cnblogs.com/now-fighting/p/3534185.html

https://packaging.python.org/discussions/install-requires-vs-requirements/

https://www.reddit.com/r/Python/comments/3uzl2a/setuppy_requirementstxt_or_a_combination/

https://stackoverflow.com/questions/2477117/pip-requirements-txt-with-alternative-index