作者: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