在Python的setuptools框架下生成egg的教程


Posted in Python onApril 13, 2015

本文介绍了 setuptools 框架的内容,它是 PEAK 的一个副项目,它提供了比 distutils 更加简单的包管理和发行功能。
开始

setuptools 模块很会 “规避”。例如,如果我们下载一个使用 setuptools 而不是使用 distutils 构建的包,那么安装就应该可以像我们期望的一样工作:通常使用 python setup.py install 就可以。为了实现这种功能,使用 setuptools 绑定在一起的包就会在归档文件中包含一个很小的引导模块 ez_setup.py。此处惟一需要注意的是 ez_setup.py 试图在后台下载并安装所需要的 setuptools —— 当然,这需要有一个连接网络的机器。如果 setuptools 早已在本地机器上安装了,那么这个后台步骤就不再需要执行;但是如果它需要手工进行安装,那么很多透明性就都丢失了。不过,大部分系统现在都有一个 Internet 连接了;为没有连接网络的机器多执行几个特殊步骤也并非特别麻烦。

setuptools 的真正优点并不在于实现 distutils 所能实现的功能 —— 尽管它 的确 增强了 distutils 的功能并简化了 setup.py 脚本中的内容。setuptools 最大的优势是它在包管理能力方面的增强。它可以使用一种更加透明的方法来查找、下载并安装依赖包;并可以在一个包的多个版本中自由进行切换,这些版本都安装在同一个系统上;也可以声明对某个包的特定版本的需求;还可以只使用一个简单的命令就能更新到某个包的最新版本。给人印象最为深刻的是,即使有些包的开发人员可能还从未考虑过任何 setuptools 兼容性问题,我们依然可以使用这些包。

下面让我们详细探讨一下。

引导

工具 ez_setup.py 是一个简单的脚本,它可以引导 setuptools 中其余部分。有点让人困惑的是,完整 setuptools 包中所提供的 easy_install 脚本与 ez_setup.py 所实现的功能是相同的。不过前者假设 setuptools 早已安装了,因此它会跳过幕后的安装过程。这两个版本都可以接受相同的参数和开关。

这个过程中的第一个步骤是下载一个小脚本 ez_setup.py:
清单 1. 下载引导脚本

% wget -q http://peak.telecommunity.com/dist/ez_setup.py

然后,就可以不带任何参数运行脚本来安装 setuptools 中其余部分了(如果不作为一个单独的步骤来执行这个步骤,在首次安装其他包时,它还是会被完成)。会看到类似于下面的内容(当然,这要取决于所使用的版本):
清单 2. 引导 setuptools

% python ez_setup.py
Downloading http://cheeseshop.python.org/packages/2.4/s/
 setuptools/setuptools-0.6b1-py2.4.egg#md5=b79a8a403e4502fbb85ee3f1941735cb
Processing setuptools-0.6b1-py2.4.egg
creating /sw/lib/python2.4/site-packages/setuptools-0.6b1-py2.4.egg
Extracting setuptools-0.6b1-py2.4.egg to /sw/lib/python2.4/site-packages
Removing setuptools 0.6a11 from easy-install.pth file
Adding setuptools 0.6b1 to easy-install.pth file
Installing easy_install script to /sw/bin
Installing easy_install-2.4 script to /sw/bin

Installed /sw/lib/python2.4/site-packages/setuptools-0.6b1-py2.4.egg
Processing dependencies for setuptools

完毕。这就是我们需要确保在系统上安装 setuptools 而需要做的工作。

安装包

对于很多 Python 包来说,要安装这些包,需要做的就是将这些包的名字作为一个参数传递给 ez_setup.py 或 easy_install。既然目前已经使用引导脚本加载了 setuptools,那就可以使用内部更加简化的 easy_install(实际上它与我们选择的版本的区别很小)了。

例如,假设希望安装 SQLObject 包。过程非常简单,如清单 3 所示。注意消息中说 SQLObject 依赖于一个名为 FormEncode 的包;所幸的是,这会被很好地解决:
清单 3. 安装一个典型的包

% easy_install SQLObject
Searching for SQLObject
Reading http://www.python.org/pypi/SQLObject/
Reading http://sqlobject.org
Best match: SQLObject 0.7.0
Downloading http://cheeseshop.python.org/packages/2.4/S/
 SQLObject/SQLObject-0.7.0-py2.4.egg#md5=71830b26083afc6ea7c53b99478e1b6a
Processing SQLObject-0.7.0-py2.4.egg
creating /sw/lib/python2.4/site-packages/SQLObject-0.7.0-py2.4.egg
Extracting SQLObject-0.7.0-py2.4.egg to /sw/lib/python2.4/site-packages
Adding SQLObject 0.7.0 to easy-install.pth file
Installing sqlobject-admin script to /sw/bin

Installed /sw/lib/python2.4/site-packages/SQLObject-0.7.0-py2.4.egg
Processing dependencies for SQLObject
Searching for FormEncode>=0.2.2
Reading http://www.python.org/pypi/FormEncode/
Reading http://formencode.org
Best match: FormEncode 0.5.1
Downloading http://cheeseshop.python.org/packages/2.4/F/
 FormEncode/FormEncode-0.5.1-py2.4.egg#md5=f8a19cbe95d0ed1b9d1759b033b7760d
Processing FormEncode-0.5.1-py2.4.egg
creating /sw/lib/python2.4/site-packages/FormEncode-0.5.1-py2.4.egg
Extracting FormEncode-0.5.1-py2.4.egg to /sw/lib/python2.4/site-packages
Adding FormEncode 0.5.1 to easy-install.pth file

Installed /sw/lib/python2.4/site-packages/FormEncode-0.5.1-py2.4.egg

正如可以从这些消息中看到的一样,easy_install 要在 www.python.org/pypi/ 上查找有关这个包的信息,然后查找真正可以下载它的地方(此处 egg 包就在 cheeseshop.python.org 上;后面将介绍有关 egg 的更多内容)。

现在不仅仅可以安装某个包的最新版本(这是默认操作)。如果愿意,还可以为 easy_install 提供一个特定的版本需求。现在让我们尝试安装 SQLObject 的一个 post-beta 版本。
清单 4. 安装某个包的最小版本

% easy_install 'SQLObject>=1.0'
Searching for SQLObject>=1.0
Reading http://www.python.org/pypi/SQLObject/
Reading http://sqlobject.org
No local packages or download links found for SQLObject>=1.0
error: Could not find suitable distribution for
 Requirement.parse('SQLObject>=1.0')

如果(在本文编写时情况就是如此)SQLObject 的最新版本小于 1.0,那么这会什么也不安装。

安装 “naive” 包

SQLObject 是可以识别 setuptools 的;但是如果要安装一个尚未兼容 setuptools 的包又该如何呢?例如,在本文之前,我从没有对自己的 “Gnosis Utilities” 使用过 setuptools。不过,现在让我们来尝试安装一下这个包,已知的只有它所在的 HTTP(或 FTP、SVN、CVS)位置(setuptools 可以理解所有这些协议)。我的下载 Web 站点上有各个 Gnosis Utilities 的版本,它们的命名采用了常见的版本风格:
清单 5. 安装不识别 setuptools 的包

% easy_install -f http://gnosis.cx/download/Gnosis_Utils.More/ Gnosis_Utils
Searching for Gnosis-Utils
Reading http://gnosis.cx/download/Gnosis_Utils.More/
Best match: Gnosis-Utils 1.2.1
Downloading http://gnosis.cx/download/Gnosis_Utils.More/
 Gnosis_Utils-1.2.1.zip
Processing Gnosis_Utils-1.2.1.zip
Running Gnosis_Utils-1.2.1/setup.py -q bdist_egg --dist-dir
 /tmp/easy_install-CCrXEs/Gnosis_Utils-1.2.1/egg-dist-tmp-Sh4DW1
zip_safe flag not set; analyzing archive contents...
gnosis.__init__: module references __file__
gnosis.magic.__init__: module references __file__
gnosis.xml.objectify.doc.__init__: module references __file__
gnosis.xml.pickle.doc.__init__: module references __file__
gnosis.xml.pickle.test.test_zdump: module references __file__
Adding Gnosis-Utils 1.2.1 to easy-install.pth file

Installed /sw/lib/python2.4/site-packages/Gnosis_Utils-1.2.1-py2.4.egg
Processing dependencies for Gnosis-Utils

所幸的是 easy_install 可以把这一切都完成得很好。它会查看给定的下载目录,识别出可用的最高版本,展开这个包,然后将其重新打包为 “egg” 格式,后者就可以用来进行安装了。导入 gnosis 现在可以在一个脚本中运行。但是假设现在需要对 Gnosis Utilities 之前的某个特定版本来测试一个脚本又该怎么做呢?这也非常简单:
清单 6. 安装一个 “naive” 包的特定版本

% easy_install -f http://gnosis.cx/download/Gnosis_Utils.More/
 "Gnosis_Utils==1.2.0"
Searching for Gnosis-Utils==1.2.0
Reading http://gnosis.cx/download/Gnosis_Utils.More/
Best match: Gnosis-Utils 1.2.0
Downloading http://gnosis.cx/download/Gnosis_Utils.More/
 Gnosis_Utils-1.2.0.zip
[...]
Removing Gnosis-Utils 1.2.1 from easy-install.pth file
Adding Gnosis-Utils 1.2.0 to easy-install.pth file

Installed /sw/lib/python2.4/site-packages/Gnosis_Utils-1.2.0-py2.4.egg
Processing dependencies for Gnosis-Utils==1.2.0

现在通常已经安装了两个版本的 Gnosis Utilities,当前活动版本是 1.2.0。将活动版本切换回 1.2.1 也非常简单:
清单 7. 在系统范围修改 “活动” 版本

% easy_install "Gnosis_Utils==1.2.1"
Searching for Gnosis-Utils==1.2.1
Best match: Gnosis-Utils 1.2.1
Processing Gnosis_Utils-1.2.1-py2.4.egg
Removing Gnosis-Utils 1.2.0 from easy-install.pth file
Adding Gnosis-Utils 1.2.1 to easy-install.pth file

Using /sw/lib/python2.4/site-packages/Gnosis_Utils-1.2.1-py2.4.egg
Processing dependencies for Gnosis-Utils==1.2.1

当然,这一次只能使一个版本是活动的。不过通过在各个脚本上面放上这样两行类似内容,就可以让脚本选择自己希望使用的版本:
清单 8. 在脚本中使用某个版本的包

from pkg_resources import require
require("Gnosis_Utils==1.2.0")

通过使用上述要求,setuptools 就可以在运行 import 语句时添加一个特定的版本(如果指定了大于比较,就是最新的可用版本)。

让包可以识别 setuptools

我会更希望让用户不需要知道 Gnosis Utilities 的下载目录就可以安装它。这 通常都可以 工作,因为 Gnosis Utilities 在 Python Cheeseshop 上有一个信息清单。不幸的是,因为没有考虑 setuptools ,所以我在 python.org 上为我的 Gnosis Utilities 建立了一个 “不匹配” 的入口 http://www.python.org/pypi/Gnosis%20Utilities/1.2.1。具体地说,这个归档文件是根据类似于 Gnosis_Utils-N.N.N.tar.gz 的模式进行命名的(这些工具也打包成了 .zip 和 .tar.bz2 文件,最新的几个版本还打包成了 win32.exe 的安装程序,所有这些文件 setuptools 都可以很好地处理)。不过 Cheeseshop 上的项目名的拼写与 “Gnosis Utilities” 稍微有点不同。实际上,在 Cheeseshop 的一个很小的管理版本的更改就会将 http://www.python.org/pypi/Gnosis_Utils/1.2.1-a 创建为一个发布后版本。发行版归档文件本身并没有什么变化,不过是在 Cheeseshop 里增加了一点元数据。只需要少量努力,就可以使用更加简单的安装程序(注意,出于测试目的,我运行了一个 easy_install -m 来删除所安装的包)。
清单 9. 简单增加对 setuptools 的识别

% easy_install Gnosis_Utils
Searching for Gnosis-Utils
Reading http://www.python.org/pypi/Gnosis_Utils/
Reading http://www.gnosis.cx/download/Gnosis_Utils.ANNOUNCE
Reading http://gnosis.cx/download/Gnosis_Utils.More/
Best match: Gnosis-Utils 1.2.1
Downloading [...]

我把这个过程剩余的部分忽略掉了,因为这与您前面看到的内容没什么两样。惟一的区别在于 easy_install 要在 Cheeseshop(换言之 www.python.org/pypi/)上寻找可以匹配指定名字的元数据,并使用这些信息来查找真正的下载位置。在这种情况中,所列出的 .ANNOUNCE 文件没有包含任何有帮助的内容,不过 easy_install 还会继续查看另一个所列的 URL,这会证明它是一个下载目录。

关于 egg

egg 是一个包含所有包数据的文件包。在理想情况中,egg 是一个使用 zip 压缩的文件,其中包括了所有需要的包文件。但是在某些情况下,setuptools 会决定(或被开关告知)包不应该是 zip 压缩的。在这些情况下,egg 只是一个简单的未曾压缩的子目录,但是里面的内容是相同的。使用单一的版本可以方便地进行转换,并可以节省一点磁盘空间,但是 egg 目录从功能和组织结构上来说都是相同的。一直使用 JAR 文件的 Java? 技术的用户会发现 egg 非常熟悉。

由于最新的 Python 版本中(需要 2.3.5+ 或 2.4)导入挂钩的更改,可以简单地通过设置 PYTHONPATH 或 sys.path 并像往常一样导入相应的包来使用 egg。如果希望采用这种方法,就不需要使用 setuptools 或 ez_setup.py 了。例如,在本文使用的工作目录中,我就为 PyYAML 包放入了一个 egg。现在我就可以使用这个包了,方法如下:
清单 10. PYTHONPATH 上的 egg

% export PYTHONPATH=~/work/dW/PyYAML-3.01-py2.4.egg
% python -c 'import yaml; print yaml.dump({"foo":"bar",1:[2,3]})'
1: [2, 3]
foo: bar

不过,PYTHONPATH 的(或者脚本或 Python shell 会话内的 sys.path的)这种操作有些脆弱。egg 的发现最好是在新一点的 .pth 文件中进行。在 site-packages/ 或 PYTHONPATH 中的任何 .pth 文件都会进行解析来执行其他导入操作,其方法类似于检查可能包含包的那些目录位置一样。如果使用 setuptools 来处理包的管理功能,那么在安装、更新、删除包时,就需要修改一个名为 easy-install.pth 的文件。而且可以按照自己喜欢的方式对这个 .pth 进行命名(只要其扩展名是 .pth 即可)。例如,下面是我的 easy-install.pth 文件的内容:
清单 11. 用作 egg 位置配置的 .pth 文件

% cat /sw/lib/python2.4/site-packages/easy-install.pth
import sys; sys.__plen = len(sys.path)
setuptools-0.6b1-py2.4.egg
SQLObject-0.7.0-py2.4.egg
FormEncode-0.5.1-py2.4.egg
Gnosis_Utils-1.2.1-py2.4.egg
import sys; new=sys.path[sys.__plen:]; del sys.path[sys.__plen:];
 p=getattr(sys,'__egginsert',0); sys.path[p:p]=new;
 sys.__egginsert = p+len(new)

这种格式有点特殊:它近似于一个 Python 脚本,但却不完全是。需要说明的是,可以在那里添加额外列出的 egg;更好的情况是,easy_install 会在运行时实现这种功能。也可以在 site-packages/ 下创建任意多个 .pth 文件;每个都可以列出有哪些 egg 是可用的。

增强安装脚本

上面所述的这种安装 setuptools naive 包的能力(请参阅 清单 6)只部分有效。也就是说,包 Gnosis_Utils 的确安装上了,但是并不完整。所有常见的功能都可以工作,但是在自动生成 egg 时却忽略了很多支持文件 —— 大部分是扩展名为 .txt 的文档和扩展名为 .xml 的测试文件(还有一些其他的 README、.rnc、.rng、.xsl 和围绕子包的文件)。在安装时,所有这些支持文件都 “最好要有”,而没有严格要求一定要有。不过,我们仍然希望能够包含所有的支持文件。

Gnosis_Utils 使用的 setup.py 脚本实际上非常复杂。除了列出基本的元数据之外,在第 467 行代码中,它还对 Python 版本的功能和 bug 进行完整测试;解决旧版本的 distutils 中的一些故障;回溯跳过对不支持部分的安装(例如,如果 pyexpat 在 Python 发行版中并没有包括);处理 OS 行结束符的转换;创建多个归档/安装程序类型;根据测试结果重新构建 MANIFEST 文件。能够实现处理这些工作的能力要感谢此包的另外一个维护人员 Frank McIngvale;这些能力可以让 Gnosis_Utils 能成功安装回 Python 1.5.1 的版本,当然前提是需要这么做(早期版本中的功能没有这么丰富)。不过此处我要向大家展示的脚本并没有像 distutils 脚本一样做这么复杂的事情:它只是简单地假设系统中已经安装了一个 “普通的” 最新版本的 Python。即使这么讲,setuptools 能让安装脚本变得如此简单还是非常吸引人。

在第一次尝试时,让我们来创建一个 setup.py 脚本,它是从 setuptools 手册中借用的,并试图使用它来创建一个 egg:
清单 12. setuptools setup.py 脚本

% cat setup.py
from setuptools import setup, find_packages
setup(
  name = "Gnosis_Utils",
  version = "1.2.2",
  packages = find_packages(),
)
% python setup.py -q bdist_egg
zip_safe flag not set; analyzing archive contents...
gnosis.__init__: module references __file__
gnosis.doc.__init__: module references __file__
gnosis.magic.__init__: module references __file__
gnosis.xml.objectify.doc.__init__: module references __file__
gnosis.xml.pickle.doc.__init__: module references __file__
gnosis.xml.pickle.test.test_zdump: module references __file__

这点努力就已经可以起作用;至少可以部分地起作用。使用这几行内容的确可以创建一个 egg,不过这个 egg 与使用 easy_install 创建的 egg 有一些相似的缺点:缺乏对不使用 .py 命名的文件的支持。因此让我们再试一次,只是需要稍微再努力一点:
清单 13. 添加缺少的 package_data

from setuptools import setup, find_packages
setup(
  name = "Gnosis_Utils",
  version = "1.2.2",
  package_data = {'':['*.*']},
  packages = find_packages(),
)

这就是需要做的所有操作。当然,根据实际情况,通常希望对它进行一些调整。例如,它可能会列出下面的内容:
清单 14. 打包特定类型文件类型

package_data = {'doc':['*.txt'], 'xml':['*.xml', 'relax/*.rnc']}

这段内容翻译一下就是:将 .txt 文件包括在 doc/ 子包中,将 .xml 文件包括在 xml/ 子包中,将所有 .rnc 文件包括在 xml/relax/ 子包中。

结束语

本文实际上只介绍了用支持 setuptools 的发行版可以执行的定制操作的表层的知识。例如,假设您现在有一个发行版(可以是首选的 egg 格式或另外一种归档类型),您就可以使用一个命令将这个归档文件和元数据上载到 Cheeseshop 上。显然,完整的 setup.py 脚本应该包含旧版本 distutils 脚本中所包含的同样详细的元数据;为了简单起见,本文跳过了这些内容,但是其参数名与 distutils 是兼容的。

尽管要完全适应 setuptools 所提供的巨大功能需要一些时间,但是实际上它确实可以让维护您自己的包和安装外来包都要比 distutils 更加简单。如果您所关心的内容仅仅是安装包,那么您所需要了解的内容在本文的介绍中已经全部包括了;只是您在描述您自己的包时可能会发现一些复杂性,不过仍然没有使用 distutils 那么复杂。

Python 相关文章推荐
python连接mysql调用存储过程示例
Mar 05 Python
python进阶教程之文本文件的读取和写入
Aug 29 Python
python实现的希尔排序算法实例
Jul 01 Python
Django中对数据查询结果进行排序的方法
Jul 17 Python
Python3访问并下载网页内容的方法
Jul 28 Python
python后端接收前端回传的文件方法
Jan 02 Python
对YOLOv3模型调用时候的python接口详解
Aug 26 Python
opencv3/C++ 平面对象识别&透视变换方式
Dec 11 Python
Python3实现监控新型冠状病毒肺炎疫情的示例代码
Feb 13 Python
Python 循环读取数据内存不足的解决方案
May 25 Python
python中出现invalid syntax报错的几种原因分析
Feb 12 Python
Python使用socket去实现TCP客户端和TCP服务端
Apr 12 Python
简单介绍Python中的RSS处理
Apr 13 #Python
Python2.x和3.x下maketrans与translate函数使用上的不同
Apr 13 #Python
使用Pyrex来扩展和加速Python程序的教程
Apr 13 #Python
在Python中使用itertools模块中的组合函数的教程
Apr 13 #Python
Python中用Spark模块的使用教程
Apr 13 #Python
简单理解Python中基于生成器的状态机
Apr 13 #Python
Python中的高级函数map/reduce使用实例
Apr 13 #Python
You might like
使用eAccelerator加密PHP程序
2008/10/03 PHP
php echo()和print()、require()和include()函数区别说明
2010/03/27 PHP
thinkPHP模型初始化实例分析
2015/12/03 PHP
PHP生成制作验证码的简单实例
2016/06/12 PHP
php使用redis的几种常见操作方式和用法示例
2020/02/20 PHP
php生成随机数/生成随机字符串的方法小结【5种方法】
2020/05/27 PHP
jQuery Ajax异步处理Json数据详解
2013/11/05 Javascript
JavaScript中的object转换函数toString()与valueOf()介绍
2014/12/31 Javascript
JavaScript中调用函数的4种方式代码实例
2015/07/08 Javascript
深入理解JS实现快速排序和去重
2016/10/17 Javascript
javascript 开发之百度地图使用到的js函数整理
2017/05/19 Javascript
限时抢购-倒计时的完整实例(分享)
2017/09/17 Javascript
vue实现歌手列表字母排序下拉滚动条侧栏排序实时更新
2019/05/14 Javascript
JavaScript接口实现方法实例分析
2020/05/16 Javascript
[15:46]教你分分钟做大人——沙王
2015/03/11 DOTA
python基础教程之简单入门说明(变量和控制语言使用方法)
2014/03/25 Python
python实现的DES加密算法和3DES加密算法实例
2015/06/03 Python
Python SQLite3数据库日期与时间常见函数用法分析
2017/08/14 Python
Django中使用CORS实现跨域请求过程解析
2019/08/05 Python
python利用7z批量解压rar的实现
2019/08/07 Python
Django models filter筛选条件详解
2020/03/16 Python
浅谈tensorflow使用张量时的一些注意点tf.concat,tf.reshape,tf.stack
2020/06/23 Python
利用Python实现字幕挂载(把字幕文件与视频合并)思路详解
2020/10/21 Python
CSS3教程(4):网页边框和网页文字阴影
2009/04/02 HTML / CSS
英国最大的电子零件及配件零售商:Partmaster
2017/04/24 全球购物
幼儿园家长评语大全
2014/04/16 职场文书
乔丹名人堂演讲稿
2014/05/24 职场文书
超市促销活动总结
2014/07/01 职场文书
政风行风建设整改方案
2014/10/27 职场文书
党的群众路线教育实践活动个人整改措施材料
2014/11/04 职场文书
党风廉政教育心得体会2016
2016/01/22 职场文书
MySQL8.0.24版本Release Note的一些改进点
2021/04/22 MySQL
Mysql systemctl start mysqld报错的问题解决
2021/06/03 MySQL
JavaScript实现栈结构详细过程
2021/12/06 Javascript
OpenCV实现常见的四种图像几何变换
2022/04/01 Python
Windows Server 2019 安装DHCP服务及相关配置
2022/04/28 Servers