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 = "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

原文地址:https://www.cnblogs.com/gqtcgq/p/7247101.html