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

Python深入:setuptools进阶的更多相关文章

  1. Python中setuptools做什么用的?

    概括 setuptools是 Python Enterprise Application Kit(PEAK)的一个副项目,它 是一组Python的 distutilsde工具的增强工具(适用于 Pyt ...

  2. ez_setup.py(安装python下setuptools用)

    #!python"""Bootstrap setuptools installation If you want to use setuptools in your pa ...

  3. Python学习笔记进阶篇——总览

    Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(Socket编程进阶&多线程.多进程) Python学习笔记——进阶篇[第八周]———进程.线程.协程篇(异常处理) Pyth ...

  4. python基础——面向对象进阶下

    python基础--面向对象进阶下 1 __setitem__,__getitem,__delitem__ 把对象操作属性模拟成字典的格式 想对比__getattr__(), __setattr__( ...

  5. python基础——面向对象进阶

    python基础--面向对象进阶 1.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 ...

  6. python面向对象编程进阶

    python面向对象编程进阶 一.isinstance(obj,cls)和issubclass(sub,super) isinstance(obj,cls)检查是否obj是否是类 cls 的对象 1 ...

  7. Python语言学习之Python入门到进阶

    人们常说Python语言简单,编写简单程序时好像也确实如此.但实际上Python绝不简单,它也是一种很复杂的语言,其功能特征非常丰富,能支持多种编程风格,在几乎所有方面都能深度定制.要想用好Pytho ...

  8. Python基础与进阶

    1 Python基础与进阶 欢迎来到Python世界 搭建编程环境 变量 | 字符串 | 注释 | 错误消除 他只用一张图,就把Python中的列表拿下了! 使用 If 语句进行条件测试 使用字典更准 ...

  9. 【转】python 面向对象(进阶篇)

    [转]python 面向对象(进阶篇) 上一篇<Python 面向对象(初级篇)>文章介绍了面向对象基本知识: 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用 类 ...

  10. 【转】Python之函数进阶

    [转]Python之函数进阶 本节内容 上一篇中介绍了Python中函数的定义.函数的调用.函数的参数以及变量的作用域等内容,现在来说下函数的一些高级特性: 递归函数 嵌套函数与闭包 匿名函数 高阶函 ...

随机推荐

  1. SASS CSS3 koala

    CSS with superpowers Sass: Syntactically Awesome Style Sheets http://sass-lang.com/ Sass is the most ...

  2. Linux command find All In One

    Linux command find All In One $ find -h # find: illegal option -- h # usage: # find [-H | -L | -P] [ ...

  3. 字节跳动-前端面试题 Multi Promise Order

    字节跳动-前端面试题 Multi Promise Order Promise Order Async/Await async function async1 () { console.log('asy ...

  4. 如何给 GitHub 添加 SSH key, 如何生成 SSH key 详细图文教程!

    如何给 GitHub 添加  SSH key, 如何生成  SSH key 详细图文教程! 一. 生成  SSH key https://ide.c9.io/xgqfrms/ 创建一个空项目:(或使用 ...

  5. 编程术语 All In One

    编程术语 All In One js 名词,术语 函数 函数签名 一个函数签名 (或类型签名,或方法签名) 定义了 函数 或 方法 的输入与输出. 一个签名可以包括: 参数 及参数的 类型 一个返回值 ...

  6. H5 广告落地页

    H5 广告落地页 Landing Page 用于承接通过付费搜索渠道点击进入的用户,所以叫落地页 什么是登陆页面? 在数字营销中,登录页面是专门为营销或广告活动创建的独立网页. 访问者单击电子邮件中的 ...

  7. js group objects in an array

    js group objects in an array js group objects in an array var groupBy = function(xs, key) { return x ...

  8. how to get selected option text in javascript

    how to get selected option text in javascript refs https://developer.mozilla.org/en-US/docs/Web/API/ ...

  9. Flutter FractionallySizedBox 设置维度比例 而不是固定的px

    本周小部件 有时您的设计需要相对的维度. FractionallySizedBox允许您将子项的大小调整为总可用空间的一小部分. Scaffold( body: Center( child: Frac ...

  10. Java中print、printf、println的区别

    Java中print.printf.println的区别 区别 print:标准输出,但不换行,不可以空参: println:标准输出,但会自动换行,可以空参,可以看做:println()相当于pri ...