1. 程式人生 > >Python深入:setuptools簡介

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