Python深入:setuptools簡介
Setuptools是Python Distutils的加強版,使開發者構建和釋出Python包更加容易,特別是當包依賴於其他包時。用setuptools構建和釋出的包與用Distutils釋出的包是類似的。包的使用者無需安裝setuptools就可以使用該包。如果使用者是從原始碼包開始構建,並且沒有安裝過setuptools的話,則只要在你的setup指令碼中包含一個bootstrap模組(ez_setup),使用者構建時就會自動下載並安裝setuptools了。
一:基本用例
下面是一個使用setuptools的簡單例子:
from setuptools import setup, find_packages setup( name = "HelloWorld", version = "0.1", packages = find_packages(), )
上面就是一個最簡單的setup指令碼,使用該指令碼,就可以產生eggs,上傳PyPI,自動包含setup.py所在目錄中的所有包等。
當然,上面的指令碼過於簡單,下面是一個稍微複雜的例子:
from setuptools import setup, find_packages setup( name = "HelloWorld", version = "0.1", packages = find_packages(), scripts = ['say_hello.py'], # Project uses reStructuredText, so ensure that the docutils get # installed or upgraded on the target machine install_requires = ['docutils>=0.3'], package_data = { # If any package contains *.txt or *.rst files, include them: '': ['*.txt', '*.rst'], # And include any *.msg files found in the 'hello' package, too: 'hello': ['*.msg'], }, # metadata for upload to PyPI author = "Me", author_email = "[email protected]", description = "This is an Example Package", license = "PSF", keywords = "hello world example examples", url = "http://example.com/HelloWorld/", # project home page, if any # could also include long_description, download_url, classifiers, etc. )
上面的指令碼包含了更多的資訊,比如依賴、資料檔案、指令碼等等,接下來的幾節會詳細解釋。
二:find_packages
對於簡單的工程,使用setup函式的packages引數一一列出安裝的包到就足夠了。但是對於大型工程來說,這卻有點麻煩,因此就有了setuptools.find_package()函式。
find_packages的引數有:一個原始碼目錄,一個include包名列表,一個exclude包名列表。如果這些引數被忽略,則原始碼目錄預設是setup.py指令碼所在目錄。該函式返回一個列表,可以賦值給packages引數。
有些工程可能會使用src或者lib目錄作為原始碼樹的子目錄,因此這些工程中,需要使用”src”或者”lib”作為find_packages()的第一個引數,當然,這種情況下還需要設定package_dir = {'':'lib'},否則的話會報錯,比如setup指令碼如下:
from setuptools import setup, find_packages
setup(
name = "HelloWorld",
version = "0.1",
package_dir = {'':'lib'},
packages = find_packages('lib'),
)
原始碼樹如下:
lib/
foo.py
heheinit.py
bar/
__init__.py
bar.py
最終生成的檔案是:
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/dependency_links.txt
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/PKG-INFO
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/SOURCES.txt
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/top_level.txt
/usr/local/lib/python2.7/dist-packages/bar/bar.py
/usr/local/lib/python2.7/dist-packages/bar/bar.pyc
/usr/local/lib/python2.7/dist-packages/bar/__init__.py
/usr/local/lib/python2.7/dist-packages/bar/__init__.pyc
如果沒有package_dir = {'':'lib'}的話,則會報錯:
error: package directory 'bar' does not exist
這是因為執行函式find_packages('lib'),返回的結果是['bar'],沒有package_dir = {'':'lib'}的話,則在setup.py所在目錄尋找包bar,自然是找不到的了。
>>> import setuptools
>>> setuptools.find_packages('lib')
['bar']
find_packages()函式遍歷目標目錄,根據include引數進行過濾,尋找Python包。對於Python3.2以及之前的版本,只有包含__init__.py檔案的目錄才會被當做包。最後,對得到的結果進行過濾,去掉匹配exclude引數的包。
include和exclude引數是包名的列表,包名中的’.’表示父子關係。比如,如果原始碼樹如下:
lib/
foo.py
__init__.py
bar/
__init__.py
bar.py
則find_packages(exclude=["lib"])(或packages = find_packages(include=["lib"])),只是排除(或包含)lib包,但是卻不會排除(或包含lib.bar)包。
三:entry points
entry points是釋出模組“宣傳”Python物件(比如函式、類)的一種方法,這些Python物件可以被其他釋出模組使用。一些可擴充套件的應用和框架可以通過特定的名字找到entry points,也可以通過釋出模組的名字來找到,找到之後即可載入使用這些物件了。
entry points要屬於某個entry points組,組其實就是一個名稱空間。在同一個entry point組內不能有相同的entry point。
entry points通過setup函式的entry_points引數來表示,這樣安裝釋出包之後,釋出包的元資料中就會包含entry points的資訊。entry points可以實現動態發現和執行外掛,自動生成可執行指令碼、生成可執行的egg檔案等功能。
setup函式的entry_points引數,可以是INI形式的字串,也可以是一個字典,字典的key是entry point group的名字,value是定義entry point的字串或者列表。
一個entry point就是”name = value”形式的字串,其中的value就是某個模組中物件的名字。另外,在”name = value”中還可以包含一個列表,表示該entry point需要用到的”extras”,當呼叫應用或者框架動態載入一個entry point的時候,”extras”表示的依賴包就會傳遞給pkg_resources.require()函式,因此如果依賴包沒有安裝的話就會打印出相應的錯誤資訊。
比如entry_points可以這樣寫:
setup(
...
entry_points = """
[blogtool.parsers]
.rst = some.nested.module:SomeClass.some_classmethod[reST]
""",
extras_require = dict(reST = "Docutils>=0.3.5")
...
)
setup(
...
entry_points = {'blogtool.parsers': '.rst = some_module:SomeClass[reST]'}
extras_require = dict(reST = "Docutils>=0.3.5")
...
)
setup(
...
entry_points = {'blogtool.parsers': ['.rst = some_module:a_func[reST]']}
extras_require = dict(reST = "Docutils>=0.3.5")
...
)
1:動態發現服務和外掛
setuptools支援向可擴充套件應用和框架中插入自己的程式碼。通過在自己的模組釋出中註冊”entry points”,就可以被應用或框架引用。
下面以向一個內容管理系統(content management system,CMS)中新增新型別的內容為例,描述如何使用entry points建立外掛。
要安裝的外掛的原始碼樹如下:
lib/
foo.py
__init__.py
bar/
__init__.py
bar.py
為了定義外掛,使用自定義的”cms.plugin”作為”entry point group”名。setup.py指令碼內容如下:
from setuptools import setup, find_packages
setup(
name = "HelloWorld",
version = "0.1",
packages = find_packages(),
entry_points = {
'cms.plugin': [
'foofun = lib.foo:foofun',
'barfun = lib.bar.bar:barfun'
]
}
)
注意,entry points引用的物件不一定非得是函式,它可以是任意的Python物件,而且entry point的名字也不一定非得是entry points引用的物件名字。
定義好setup.py之後,就可以通過python setup.py install安裝該包,生成的檔案是:
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg
/usr/local/lib/python2.7/dist-packages/easy-install.pth
其中的HelloWorld-0.1-py2.7.egg是個標準的ZIP檔案,解壓後生成:
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/dependency_links.txt
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/entry_points.txt
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/PKG-INFO
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/SOURCES.txt
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg-info/top_level.txt
/usr/local/lib/python2.7/dist-packages/lib/__init__.py
/usr/local/lib/python2.7/dist-packages/lib/__init__.pyc
/usr/local/lib/python2.7/dist-packages/lib/foo.py
/usr/local/lib/python2.7/dist-packages/lib/foo.pyc
/usr/local/lib/python2.7/dist-packages/lib/bar/__init__.py
/usr/local/lib/python2.7/dist-packages/lib/bar/__init__.pyc
/usr/local/lib/python2.7/dist-packages/lib/bar/bar.py
/usr/local/lib/python2.7/dist-packages/lib/bar/bar.pyc
外掛安裝好之後,就可以在CMS中編寫載入外掛的程式碼了。既可以通過釋出的名字和版本號找到外掛,也可以通過entry point group和entry point的名字,一般使用後者,比如動態載入外掛的程式碼如下:
from pkg_resources import iter_entry_points
for entry_point in iter_entry_points(group='cms.plugin', name=None):
print(entry_point)
fun = entry_point.load()
fun()
執行該指令碼,結果如下:
barfun = lib.bar.bar:barfun
hello, this is barfun
foofun = lib.foo:foofun
hello, this is foofun
也可以通過iter_entry_points中的name引數,載入特定的entry_points
2:自動建立指令碼
setuptools能夠自動生成可執行指令碼,在Windows平臺上他甚至能建立一個exe檔案。這就是通過setup.py指令碼中的”entry points”實現的,它指明瞭生成的指令碼需要引入並執行的函式。
比如,原始碼樹如下:
lib/
foo.py
__init__.py
bar/
__init__.py
bar.py
其中的foo.py內容如下:
def foofun():
print 'hehe, this is foofun'
bar.py內容如下:
def barfun():
print 'hehe, this is barfun'
要建立兩個控制檯指令碼foohehe和barhehe,setup指令碼內容如下:
from setuptools import setup, find_packages
setup(
name = "HelloWorld",
version = "0.1",
packages = find_packages(),
entry_points = {
'console_scripts': [
'foohehe = lib.foo:foofun',
'barhehe = lib.bar.bar:barfun',
]
}
)
注意要建立控制檯指令碼,只能使用“console_scripts”作為entry point group名,要建立GUI指令碼,只能使用“gui_scripts”作為entry point group名。否則就不會生成相應的指令碼或者exe檔案。
安裝之後,生成的檔案是:
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg
/usr/local/lib/python2.7/dist-packages/easy-install.pth
/usr/local/bin/foohehe
/usr/local/bin/barhehe
其中的HelloWorld-0.1-py2.7.egg是個標準的ZIP檔案,解壓後生成的檔案與上例相同。
其中的foohehe和barhehe是兩個可執行python指令碼,foohehe內容如下:
#!/usr/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'HelloWorld==0.1','console_scripts','foohehe'
__requires__ = 'HelloWorld==0.1'
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.exit(
load_entry_point('HelloWorld==0.1', 'console_scripts', 'foohehe')()
)
barhehe內容如下:
#!/usr/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'HelloWorld==0.1','console_scripts','barhehe'
__requires__ = 'HelloWorld==0.1'
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.exit(
load_entry_point('HelloWorld==0.1', 'console_scripts', 'barhehe')()
)
執行foohehe和barhehe都可以得到正確的列印結果。如果在Windows上安裝,則會建立相應的exe檔案和py檔案。exe檔案將會使用Python執行py檔案。
pkg_resources還提供了很多有關entry points的API,具體可以參閱:https://pythonhosted.org/setuptools/pkg_resources.html#convenience-api
3:生成可執行的egg檔案
還可以通過entry point建立直接可執行的egg檔案。比如,還是上面的例子,包的原始碼樹和內容都沒有變,只不過setup.py的內容是:
from setuptools import setup, find_packages
setup(
name = "HelloWorld",
version = "0.1",
packages = find_packages(),
entry_points = {
'setuptools.installation': [
'eggsecutable = lib.foo:foofun',
]
}
)
安裝之後,生成的檔案是:
/usr/local/lib/python2.7/dist-packages/HelloWorld-0.1-py2.7.egg
/usr/local/lib/python2.7/dist-packages/easy-install.pth
其中的HelloWorld-0.1-py2.7.egg也是個ZIP壓縮檔案,解壓後的檔案與上例相同,不同的地方在於,HelloWorld-0.1-py2.7.egg檔案除了包含壓縮資料之外,還包含了一個shell指令碼。ZIP檔案支援在壓縮資料之外附加額外的資料:https://en.wikipedia.org/wiki/Zip_(file_format)#Combination_with_other_file_formats)
用UltraEdit檢視HelloWorld-0.1-py2.7.egg的內容,發現它在壓縮資料檔案頭(0x504B0304)之前,包含了下面的內容:
#!/bin/sh
if [ `basename $0` = "HelloWorld-0.1-py2.7.egg" ]
then exec python2.7 -c "import sys, os; sys.path.insert(0, os.path.abspath('$0')); from lib.foo import foofun; sys.exit(foofun())" "$@"
else
echo $0 is not the correct name for this egg file.
echo Please rename it back to HelloWorld-0.1-py2.7.egg and try again.
exec false
fi
這是一段shell指令碼,因此,將該egg檔案使用/bin/bash執行,它會執行lib.foo:foofun函式:
# /bin/bash HelloWorld-0.1-py2.7.egg
hello, this is foofun
注意使用entry_points建立可執行的egg檔案時,其中的”setuptools.installation”和”eggsecutable”是固定寫法,不可更改,否則不會起作用。
從上面的指令碼內容可見,shell指令碼對檔名進行了檢查,因此要想直接執行該egg檔案,不能改名,不能使用符號連結,否則會執行失敗。
這種特性主要是為了支援ez_setup,也就是在非Windows上安裝setuptools本身,當然也有可能在其他專案中會使用到。
四:依賴
setuptools支援在安裝釋出包時順帶安裝它的依賴包,且會在Python Eggs中包含依賴的資訊,這樣像easyinstall這樣的包管理工具就可以使用這些資訊了。
setuptools和pkg_resources使用一種常見的語法來說明依賴。首先是一個釋出包的PyPI名字,後跟一個可選的列表,列表中包含了額外的資訊,之後可選的跟一系列逗號分隔的版本說明。版本說明就是由符號<, >, <=, >=, == 或 != 跟一個版本號。
一個專案的版本說明在內部會以升序的版本號進行排序,用來生成一個可接受的版本範圍,並且會將相鄰的冗餘條件進行結合(比如”>1,>2”會變為”>1”,”<2,<3”會變為”<3”)。”!=”表示的版本會在範圍內被刪除。生成版本範圍之後,就會檢查專案的版本號是否在該範圍內。注意,如果提供的版本資訊有衝突(比如 “<2,>=2” 或“==2,!=2”),這是無意義的,並且會產生奇怪的結果。
下面是一些說明依賴的例子:
docutils >= 0.3
BazSpam ==1.1, ==1.2, ==1.3, ==1.4, ==1.5, ==1.6, ==1.7
PEAK[FastCGI, reST]>=0.5a4
setuptools==0.5a7
1:基本用法
當安裝你的釋出包的時候,如果setup.py中指明瞭本包的依賴,則不管使用easyinstall,還是setup.py install,還是setup.py develop,所有未安裝的依賴,都會通過PyPI定位,下載,構建並且安裝,安裝好的釋出包的Egg中,還會生成一個包含依賴關係的元資料檔案。
使用setup函式的install_requires引數來指明依賴,該引數包含說明依賴的字串或列表,如果在一個字串中包含了多個依賴,則每個依賴必須獨佔一行。
比如下面的setup.py:
from setuptools import setup, find_packages
setup(
name = "HelloWorld",
version = "0.1",
packages = find_packages(),
install_requires = "foobar",
)
說明該HelloWorld釋出包依賴於foobar模組,用python setuo.py install安裝時,如果還沒有安裝過foobar,則會在PyPI以及其他模組庫中尋找foobar模組,如果找不到則會報錯:
…
Processing dependencies for HelloWorld==0.1
Searching for foobar
Reading https://pypi.python.org/simple/foobar/
Reading http://ziade.org
No local packages or download links found for foobar
error: Could not find suitable distribution for Requirement.parse('foobar')
如果已經安裝好了foobar包的話,則會列印:
…
Processing dependencies for HelloWorld==0.1
Searching for foobar==0.1
Best match: foobar 0.1
Processing foobar-0.1-py2.7.egg
foobar 0.1 is already the active version in easy-install.pth
Using /root/.local/lib/python2.7/site-packages/foobar-0.1-py2.7.egg
Finished processing dependencies for HelloWorld==0.1
如果依賴的模組沒有在PyPI中註冊,則可以通過setup()的dependency_links引數,提供一個下載該模組的URL。dependency_links選項是一個包含URL字串的列表,URL可以是直接可下載檔案的URL,或者是一個包含下載連結的web頁面,還可以是模組庫的URL。比如:
setup(
...
dependency_links = [
"http://peak.telecommunity.com/snapshots/"
],
)
2:動態依賴
如果釋出包中有指令碼的話,則該指令碼在執行時會驗證依賴是否滿足,並將相應版本的依賴包的路徑新增到sys.path中。比如下面自動生成指令碼的setup.py:
from setuptools import setup, find_packages
setup(
name = "Project-A",
version = "0.1",
packages = find_packages(),
install_requires = "foobar",
entry_points = {
'console_scripts': [
'foofun = lib.foo:foofun',
'barfun = lib.bar.bar:barfun'
]
}
)
安裝Project-A的時候,就會順帶安裝foobar模組,如果安裝都成功了,就會生成指令碼/usr/bin/foofun和/usr/bin/barfun。
在執行指令碼foofun和barfun時就會檢視依賴是否滿足。比如安裝成功後,將foobar的egg檔案刪除,則執行foofun或者barfun的時候就會報錯:
Traceback (most recent call last):
File "./barfun", line 5, in <module>
from pkg_resources import load_entry_point
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 3084, in <module>
@_call_aside
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 3070, in _call_aside
f(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 3097, in _initialize_master_working_set
working_set = WorkingSet._build_master()
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 651, in _build_master
ws.require(__requires__)
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 952, in require
needed = self.resolve(parse_requirements(requirements))
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 839, in resolve
raise DistributionNotFound(req, requirers)
pkg_resources.DistributionNotFound: The 'foobar' distribution was not found and is required by Project-A
3:”extras”
還可以指定非強制性的依賴,比如某個釋出包A,若在已經安裝了ReportLab 的情況下,就可選的支援PDF輸出,這種可選的特徵叫做”extras”,setuptools允許定義”extras”的依賴。”extras”的依賴不會自動安裝,除非其他釋出包B的setup.py中用install_requires明確的指定依賴釋出包A的”extras”特性。
使用setup函式的extras_require引數來說明” extras”, extras_require是一個字典,key是”extra”的名字,value就是描述依賴的字串或者字串列表。比如下面的Project-A就提供了可選的PDF支援:
setup(
name="Project-A",
...
extras_require = {
'PDF': ["ReportLab>=1.2", "RXP"]
}
)
在安裝Project-A的時候,”extras”的依賴ReportLab不會自動安裝,除非其他的釋出包的setup.py中的明確的指明,比如:
setup(
name="Project-B",
install_requires = ["Project-A[PDF]"],
...
)
這樣在安裝Project-B時,如果沒有安裝過Project-A,就會在PyPI中尋找專案Project-A和ReportLab。如果專案A已經安裝過,但ReportLab未安裝,則會去尋找ReportLab:
…
Processing dependencies for Project-B
Searching for ReportLab>=1.2
Reading https://pypi.python.org/simple/ReportLab/
…
注意,如果Project-A的PDF特性的依賴改變了,比如變成了tinyobj,則需要重新安裝一遍Project-A,否則安裝Project-B時,還是會尋找ReportLab。
注意,如果某個extra的特性不依賴於其他模組,則可以這樣寫:
setup(
name="Project-A",
...
extras_require = {
'PDF': []
}
)
extras可以用entry_point來指定動態依賴。比如下面自動生成指令碼的例子:
setup(
name="Project-A",
...
entry_points = {
'console_scripts': [
'rst2pdf = project_a.tools.pdfgen [PDF]',
'rst2html = project_a.tools.htmlgen',
],
},
extras_require = {
'PDF': []
}
)
這種情況,只有在執行rst2pdf指令碼的時候,才會嘗試解決PDF依賴,如果無法找到依賴,則會報錯。執行rst2html就不需要依賴。
執行註冊的外掛時,也是動態檢查依賴的例子,比如:
from setuptools import setup, find_packages
setup(
name = "HelloWorld",
version = "0.1",
packages = find_packages(),
entry_points = {
'cms.plugin': [
'foofun = lib.foo:foofun[pdf]',
'barfun = lib.bar.bar:barfun'
]
},
extras_require = dict(pdf = "foobar")
)
動態載入外掛的程式碼如下:
from pkg_resources import iter_entry_points
for entry_point in iter_entry_points(group='cms.plugin', name='foofun'):
print(entry_point)
fun = entry_point.load()
fun()
如果沒有安裝foobar模組,則執行上面的指令碼就會報錯:
# python testplugin.py
foofun = lib.foo:foofun [pdf]
Traceback (most recent call last):
File "testplugin.py", line 7, in <module>
fun = entry_point.load()
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2354, in load
self.require(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 2371, in require
items = working_set.resolve(reqs, env, installer)
File "/usr/lib/python2.7/site-packages/pkg_resources/__init__.py", line 839, in resolve
raise DistributionNotFound(req, requirers)
pkg_resources.DistributionNotFound: The 'foobar' distribution was not found and is required by the application
五:easy_install
easy_install是setuptools中的一個模組,使用它可以自動從網上下載、構建、安裝和管理Python釋出包。安裝完setuptools之後,easy_install就會自動安裝到/usr/bin中。
使用easy_install安裝包,只需要提供檔名或者一個URL即可。如果僅提供了檔名,則該工具會在PyPI中搜索該包的最新版本,然後自動的下載、構建並且安裝。比如:
easy_install SQLObject
也可以指定其他下載站點的URL,比如:
easy_install -f http://pythonpaste.org/package_index.html SQLObject
或者是:
easy_install http://example.com/path/to/MyPackage-1.2.3.tgz
或者,可以安裝本地的egg檔案,比如:
easy_install /my_downloads/OtherPackage-3.2.1-py2.3.egg
可以將一個已經安裝過的包更新到PyPI的最新版本:
easy_install --upgrade PyProtocols
如果是想解除安裝某個釋出包,則需要先執行:
easy_install -m PackageName
這就能保證Python不會繼續搜尋你要解除安裝的包了。執行該命令之後,就可以安全的刪除.egg檔案或目錄,以及相應的可執行指令碼。
更多關於easy_install的資訊,參閱:https://setuptools.pypa.io/en/latest/easy_install.html
六:版本號
版本號的作用,就是能使setuptools和easyinstall可以分辨出包的新舊關係。
版本號是由一系列的釋出號、prerelease標籤、postrelease標籤交替組成的。
釋出號是一系列數字和點號(‘.’)穿插組成。比如2.4、0.5等。這些釋出號被當做數字來看待,所以2.1和2.1.0是同一版本號的不同寫法,表示的是釋出號2之後的第一個子釋出。但是像2.10表示的是釋出號2之後的第10個子釋出,所以2.10要比2.1.0更新。注意在數字之前緊挨著的0是會被忽略的,所以2.01等同於2.1,但是不同於2.0.1。
prerelease標籤是按照字母順序在”final”之前的一系列字母(單詞),比如alpha,beta,a,c,dev等等。釋出號和pre-release標籤之間可以為空,也可以是’.’或’-’。所以2.4c1、2.4.c1和2.4-c1,它們都是等同的,都表示版本2.4的1號候選(candidate )版本。另外,有三個特殊的prerelease標籤被看做與字母’c’(candidate)一樣:pre、preview和rc。所以版本號2.4rc1,2.4pre1和2.4preview1,在setuptools看來它們和2.4c1是一樣的。
帶有prerelease標籤的版本號要比不帶該標籤的相同版本號要舊,所以2.4a1, 2.4b1以及2.4c1都比2.4更舊。
相應的,postrelease標籤是按照字母順序在”final”之後的一系列字母(單詞),或者可以是單獨的一個’-’。postrelease標籤經常被用來分隔釋出號和補丁號、埠號、構件號、修訂號以及時間戳等。比如2.4-r1263表示繼2.4之後釋出的第1263號修訂版本,或者可以用2.4-20051127表示一個後續釋出的時間戳。
帶有postrelease標籤的版本號要比不帶該標籤的相同版本號更新,所以2.4-1, 2.4p13都比2.4更新,但是要比2.4.1更舊(2.4.1的釋出號更高)。
注意在prerelease標籤或postrelease標籤之後,也可以跟另外的釋出號,然後釋出號之後又可以跟prerelease或postrelease標籤。比如0.6a9.dev-r41475,因dev是prerelease標籤,所以該版本要比0.6a9要舊,-r41475是postrelease標籤,所以該版本要比0.6a9.dev更新。
注意,不要把兩個prerelease標籤寫在一起,它們之間要有點號、數字或者’-‘分隔。比如1.9adev與1.9a.dev是不同的。1.9a0dev、1.9a.dev、1.9a0dev和1.9.a.dev是相同的。
可以使用函式pkg_resources.parse_version()來測試不同版本號之間的關係:
>>> from pkg_resources import parse_version
>>> parse_version('1.9.a.dev') == parse_version('1.9a0dev')
True
>>> parse_version('2.1-rc2') < parse_version('2.1')
True
>>> parse_version('0.6a9dev-r41475') < parse_version('0.6a9')
True
七:其他
其他有關Setuptools的資訊,可查閱官方文件:https://setuptools.pypa.io/en/latest/setuptools.html