作者:gqtcgq
來源:CSDN
原文:https://blog.csdn.net/gqtcgq/article/details/49519685
Setuptools是Python Distutils的加強版,使開發者構建和發布Python包更加容易,特別是當包依賴於其他包時。用setuptools構建和發布的包與用Distutils發布的包是類似的。包的使用者無需安裝setuptools就可以使用該包。如果用戶是從源碼包開始構建,並且沒有安裝過setuptools的話,則只要在你的setup腳本中包含一個bootstrap模塊(ez_setup),用戶構建時就會自動下載並安裝setuptools了。
一:基本用例
下面是一個使用setuptools的簡單例子:
├── demo │ └─ myapp │ └── __init__.py └── setup.py
1.創建一個demo文件夾: mkdir demo
2.新建一個setup.py文件 ,內容如下
from setuptools import setup, find_packages setup( name='HelloWorld', version='0.1', packages=find_packages(), author='ZBJ', url='None', author_email='None' )
3.在demo文件夾下再新建一個文件夾:myapp和__init__.py
4.myapp下的__init__.py如下:
def test(): print("Hello World!") if __name__ == '__main__': test()
5.檢查setup.py是否有錯誤或警告:python setup.py check
6.執行 python setup.py bdist_egg 即可打包一個test的包了(在dist中生成的是egg包,.egg文件其實是一個zip包)
7.解壓egg包后,安裝該包:python setup.py install
上面就是一個最簡單的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 = "me@example.com",
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