本文介紹了 setuptools 框架的內(nèi)容,它是 PEAK 的一個副項目,它提供了比 distutils 更加簡單的包管理和發(fā)行功能。
開始
setuptools 模塊很會 “規(guī)避”。例如,如果我們下載一個使用 setuptools 而不是使用 distutils 構(gòu)建的包,那么安裝就應(yīng)該可以像我們期望的一樣工作:通常使用 python setup.py install 就可以。為了實現(xiàn)這種功能,使用 setuptools 綁定在一起的包就會在歸檔文件中包含一個很小的引導(dǎo)模塊 ez_setup.py。此處惟一需要注意的是 ez_setup.py 試圖在后臺下載并安裝所需要的 setuptools —— 當(dāng)然,這需要有一個連接網(wǎng)絡(luò)的機器。如果 setuptools 早已在本地機器上安裝了,那么這個后臺步驟就不再需要執(zhí)行;但是如果它需要手工進行安裝,那么很多透明性就都丟失了。不過,大部分系統(tǒng)現(xiàn)在都有一個 Internet 連接了;為沒有連接網(wǎng)絡(luò)的機器多執(zhí)行幾個特殊步驟也并非特別麻煩。
setuptools 的真正優(yōu)點并不在于實現(xiàn) distutils 所能實現(xiàn)的功能 —— 盡管它 的確 增強了 distutils 的功能并簡化了 setup.py 腳本中的內(nèi)容。setuptools 最大的優(yōu)勢是它在包管理能力方面的增強。它可以使用一種更加透明的方法來查找、下載并安裝依賴包;并可以在一個包的多個版本中自由進行切換,這些版本都安裝在同一個系統(tǒng)上;也可以聲明對某個包的特定版本的需求;還可以只使用一個簡單的命令就能更新到某個包的最新版本。給人印象最為深刻的是,即使有些包的開發(fā)人員可能還從未考慮過任何 setuptools 兼容性問題,我們依然可以使用這些包。
下面讓我們詳細探討一下。
引導(dǎo)
工具 ez_setup.py 是一個簡單的腳本,它可以引導(dǎo) setuptools 中其余部分。有點讓人困惑的是,完整 setuptools 包中所提供的 easy_install 腳本與 ez_setup.py 所實現(xiàn)的功能是相同的。不過前者假設(shè) setuptools 早已安裝了,因此它會跳過幕后的安裝過程。這兩個版本都可以接受相同的參數(shù)和開關(guān)。
這個過程中的第一個步驟是下載一個小腳本 ez_setup.py:
清單 1. 下載引導(dǎo)腳本
1
|
% wget -q http: //peak .telecommunity.com /dist/ez_setup .py |
然后,就可以不帶任何參數(shù)運行腳本來安裝 setuptools 中其余部分了(如果不作為一個單獨的步驟來執(zhí)行這個步驟,在首次安裝其他包時,它還是會被完成)。會看到類似于下面的內(nèi)容(當(dāng)然,這要取決于所使用的版本):
清單 2. 引導(dǎo) setuptools
1
2
3
4
5
6
7
8
9
10
11
12
13
|
% 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 |
完畢。這就是我們需要確保在系統(tǒng)上安裝 setuptools 而需要做的工作。
安裝包
對于很多 Python 包來說,要安裝這些包,需要做的就是將這些包的名字作為一個參數(shù)傳遞給 ez_setup.py 或 easy_install。既然目前已經(jīng)使用引導(dǎo)腳本加載了 setuptools,那就可以使用內(nèi)部更加簡化的 easy_install(實際上它與我們選擇的版本的區(qū)別很小)了。
例如,假設(shè)希望安裝 SQLObject 包。過程非常簡單,如清單 3 所示。注意消息中說 SQLObject 依賴于一個名為 FormEncode 的包;所幸的是,這會被很好地解決:
清單 3. 安裝一個典型的包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
% 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/ 上查找有關(guān)這個包的信息,然后查找真正可以下載它的地方(此處 egg 包就在 cheeseshop.python.org 上;后面將介紹有關(guān) egg 的更多內(nèi)容)。
現(xiàn)在不僅僅可以安裝某個包的最新版本(這是默認操作)。如果愿意,還可以為 easy_install 提供一個特定的版本需求。現(xiàn)在讓我們嘗試安裝 SQLObject 的一個 post-beta 版本。
清單 4. 安裝某個包的最小版本
1
2
3
4
5
6
7
|
% 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。不過,現(xiàn)在讓我們來嘗試安裝一下這個包,已知的只有它所在的 HTTP(或 FTP、SVN、CVS)位置(setuptools 可以理解所有這些協(xié)議)。我的下載 Web 站點上有各個 Gnosis Utilities 的版本,它們的命名采用了常見的版本風(fēng)格:
清單 5. 安裝不識別 setuptools 的包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
% 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” 格式,后者就可以用來進行安裝了。導(dǎo)入 gnosis 現(xiàn)在可以在一個腳本中運行。但是假設(shè)現(xiàn)在需要對 Gnosis Utilities 之前的某個特定版本來測試一個腳本又該怎么做呢?這也非常簡單:
清單 6. 安裝一個 “naive” 包的特定版本
1
2
3
4
5
6
7
8
9
10
11
12
13
|
% 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 |
現(xiàn)在通常已經(jīng)安裝了兩個版本的 Gnosis Utilities,當(dāng)前活動版本是 1.2.0。將活動版本切換回 1.2.1 也非常簡單:
清單 7. 在系統(tǒng)范圍修改 “活動” 版本
1
2
3
4
5
6
7
8
9
|
% 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 |
當(dāng)然,這一次只能使一個版本是活動的。不過通過在各個腳本上面放上這樣兩行類似內(nèi)容,就可以讓腳本選擇自己希望使用的版本:
清單 8. 在腳本中使用某個版本的包
1
2
|
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。具體地說,這個歸檔文件是根據(jù)類似于 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 創(chuàng)建為一個發(fā)布后版本。發(fā)行版歸檔文件本身并沒有什么變化,不過是在 Cheeseshop 里增加了一點元數(shù)據(jù)。只需要少量努力,就可以使用更加簡單的安裝程序(注意,出于測試目的,我運行了一個 easy_install -m 來刪除所安裝的包)。
清單 9. 簡單增加對 setuptools 的識別
1
2
3
4
5
6
7
|
% 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 [...] |
我把這個過程剩余的部分忽略掉了,因為這與您前面看到的內(nèi)容沒什么兩樣。惟一的區(qū)別在于 easy_install 要在 Cheeseshop(換言之 www.python.org/pypi/)上尋找可以匹配指定名字的元數(shù)據(jù),并使用這些信息來查找真正的下載位置。在這種情況中,所列出的 .ANNOUNCE 文件沒有包含任何有幫助的內(nèi)容,不過 easy_install 還會繼續(xù)查看另一個所列的 URL,這會證明它是一個下載目錄。
關(guān)于 egg
egg 是一個包含所有包數(shù)據(jù)的文件包。在理想情況中,egg 是一個使用 zip 壓縮的文件,其中包括了所有需要的包文件。但是在某些情況下,setuptools 會決定(或被開關(guān)告知)包不應(yīng)該是 zip 壓縮的。在這些情況下,egg 只是一個簡單的未曾壓縮的子目錄,但是里面的內(nèi)容是相同的。使用單一的版本可以方便地進行轉(zhuǎn)換,并可以節(jié)省一點磁盤空間,但是 egg 目錄從功能和組織結(jié)構(gòu)上來說都是相同的。一直使用 JAR 文件的 Java? 技術(shù)的用戶會發(fā)現(xiàn) egg 非常熟悉。
由于最新的 Python 版本中(需要 2.3.5+ 或 2.4)導(dǎo)入掛鉤的更改,可以簡單地通過設(shè)置 PYTHONPATH 或 sys.path 并像往常一樣導(dǎo)入相應(yīng)的包來使用 egg。如果希望采用這種方法,就不需要使用 setuptools 或 ez_setup.py 了。例如,在本文使用的工作目錄中,我就為 PyYAML 包放入了一個 egg。現(xiàn)在我就可以使用這個包了,方法如下:
清單 10. PYTHONPATH 上的 egg
1
2
3
4
|
% 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 會話內(nèi)的 sys.path的)這種操作有些脆弱。egg 的發(fā)現(xiàn)最好是在新一點的 .pth 文件中進行。在 site-packages/ 或 PYTHONPATH 中的任何 .pth 文件都會進行解析來執(zhí)行其他導(dǎo)入操作,其方法類似于檢查可能包含包的那些目錄位置一樣。如果使用 setuptools 來處理包的管理功能,那么在安裝、更新、刪除包時,就需要修改一個名為 easy-install.pth 的文件。而且可以按照自己喜歡的方式對這個 .pth 進行命名(只要其擴展名是 .pth 即可)。例如,下面是我的 easy-install.pth 文件的內(nèi)容:
清單 11. 用作 egg 位置配置的 .pth 文件
1
2
3
4
5
6
7
8
9
|
% 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 會在運行時實現(xiàn)這種功能。也可以在 site-packages/ 下創(chuàng)建任意多個 .pth 文件;每個都可以列出有哪些 egg 是可用的。
增強安裝腳本
上面所述的這種安裝 setuptools naive 包的能力(請參閱 清單 6)只部分有效。也就是說,包 Gnosis_Utils 的確安裝上了,但是并不完整。所有常見的功能都可以工作,但是在自動生成 egg 時卻忽略了很多支持文件 —— 大部分是擴展名為 .txt 的文檔和擴展名為 .xml 的測試文件(還有一些其他的 README、.rnc、.rng、.xsl 和圍繞子包的文件)。在安裝時,所有這些支持文件都 “最好要有”,而沒有嚴格要求一定要有。不過,我們?nèi)匀幌M軌虬械闹С治募?/p>
Gnosis_Utils 使用的 setup.py 腳本實際上非常復(fù)雜。除了列出基本的元數(shù)據(jù)之外,在第 467 行代碼中,它還對 Python 版本的功能和 bug 進行完整測試;解決舊版本的 distutils 中的一些故障;回溯跳過對不支持部分的安裝(例如,如果 pyexpat 在 Python 發(fā)行版中并沒有包括);處理 OS 行結(jié)束符的轉(zhuǎn)換;創(chuàng)建多個歸檔/安裝程序類型;根據(jù)測試結(jié)果重新構(gòu)建 MANIFEST 文件。能夠?qū)崿F(xiàn)處理這些工作的能力要感謝此包的另外一個維護人員 Frank McIngvale;這些能力可以讓 Gnosis_Utils 能成功安裝回 Python 1.5.1 的版本,當(dāng)然前提是需要這么做(早期版本中的功能沒有這么豐富)。不過此處我要向大家展示的腳本并沒有像 distutils 腳本一樣做這么復(fù)雜的事情:它只是簡單地假設(shè)系統(tǒng)中已經(jīng)安裝了一個 “普通的” 最新版本的 Python。即使這么講,setuptools 能讓安裝腳本變得如此簡單還是非常吸引人。
在第一次嘗試時,讓我們來創(chuàng)建一個 setup.py 腳本,它是從 setuptools 手冊中借用的,并試圖使用它來創(chuàng)建一個 egg:
清單 12. setuptools setup.py 腳本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
% 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__ |
這點努力就已經(jīng)可以起作用;至少可以部分地起作用。使用這幾行內(nèi)容的確可以創(chuàng)建一個 egg,不過這個 egg 與使用 easy_install 創(chuàng)建的 egg 有一些相似的缺點:缺乏對不使用 .py 命名的文件的支持。因此讓我們再試一次,只是需要稍微再努力一點:
清單 13. 添加缺少的 package_data
1
2
3
4
5
6
7
|
from setuptools import setup, find_packages setup( name = "Gnosis_Utils" , version = "1.2.2" , package_data = {' ':[' * . * ']}, packages = find_packages(), ) |
這就是需要做的所有操作。當(dāng)然,根據(jù)實際情況,通常希望對它進行一些調(diào)整。例如,它可能會列出下面的內(nèi)容:
清單 14. 打包特定類型文件類型
1
|
package_data = { 'doc' :[ '*.txt' ], 'xml' :[ '*.xml' , 'relax/*.rnc' ]} |
這段內(nèi)容翻譯一下就是:將 .txt 文件包括在 doc/ 子包中,將 .xml 文件包括在 xml/ 子包中,將所有 .rnc 文件包括在 xml/relax/ 子包中。
結(jié)束語
本文實際上只介紹了用支持 setuptools 的發(fā)行版可以執(zhí)行的定制操作的表層的知識。例如,假設(shè)您現(xiàn)在有一個發(fā)行版(可以是首選的 egg 格式或另外一種歸檔類型),您就可以使用一個命令將這個歸檔文件和元數(shù)據(jù)上載到 Cheeseshop 上。顯然,完整的 setup.py 腳本應(yīng)該包含舊版本 distutils 腳本中所包含的同樣詳細的元數(shù)據(jù);為了簡單起見,本文跳過了這些內(nèi)容,但是其參數(shù)名與 distutils 是兼容的。
盡管要完全適應(yīng) setuptools 所提供的巨大功能需要一些時間,但是實際上它確實可以讓維護您自己的包和安裝外來包都要比 distutils 更加簡單。如果您所關(guān)心的內(nèi)容僅僅是安裝包,那么您所需要了解的內(nèi)容在本文的介紹中已經(jīng)全部包括了;只是您在描述您自己的包時可能會發(fā)現(xiàn)一些復(fù)雜性,不過仍然沒有使用 distutils 那么復(fù)雜。