原文章:点击打开链接
4.setuptools进阶
在上例中,在前两例中,我们基本都使用setup()的默认参数,这只能写一些简单的egg。一旦我们的project逐渐变大以后,维护起来就有点复杂了,下面是setup()的其他参数,我们可以学习一下
使用find_packages()
对于简单工程来说,手动增加packages参数很容易,刚刚我们用到了这个函数,它默认在和setup.py同一目录下搜索各个含有__init__.py
的包。其实我们可以将包统一放在一个src目录中,另外,这个包内可能还有aaa.txt文件和data数据文件夹。
demo
├── setup.py
└── src
└── demo
├── __init__.py
├── aaa.txt
└── data
├── abc.dat
└── abcd.dat
如果不加控制,则setuptools只会将__init__.py
加入到egg中,想要将这些文件都添加,需要修改setup.py
from setuptools import setup, find_packages
setup(
packages = find_packages('src'), # 包含所有src中的包
package_dir = {'':'src'}, # 告诉distutils包都在src下
package_data = {
# 任何包中含有.txt文件,都包含它
'': ['*.txt'],
# 包含demo包data文件夹中的 *.dat文件
'demo': ['data/*.dat'],
}
)
这样,在生成的egg中就包含了所需文件了。看看:
Archive: dist/demo-0.0.1-py2.7.egg
Length Date Time Name
-------- ---- ---- ----
88 06-07-13 23:40 demo/__init__.py
347 06-07-13 23:52 demo/__init__.pyc
0 06-07-13 23:45 demo/aaa.txt
0 06-07-13 23:46 demo/data/abc.dat
0 06-07-13 23:46 demo/data/abcd.dat
1 06-07-13 23:52 EGG-INFO/dependency_links.txt
178 06-07-13 23:52 EGG-INFO/PKG-INFO
157 06-07-13 23:52 EGG-INFO/SOURCES.txt
5 06-07-13 23:52 EGG-INFO/top_level.txt
1 06-07-13 23:52 EGG-INFO/zip-safe
-------- -------
777 10 files
另外,也可以排除一些特定的包,如果在src中再增加一个tests包,可以通过exclude来排除它,
find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"])
使用entry_points
一个字典,从entry point组名映射道一个表示entry point的字符串或字符串列表。Entry points是用来支持动态发现服务和插件的,也用来支持自动生成脚本。这个还是看例子比较好理解:
setup(
entry_points = {
'console_scripts': [
'foo = demo:test',
'bar = demo:test',
],
'gui_scripts': [
'baz = demo:test',
]
}
)
修改setup.py
增加以上内容以后,再次安装这个egg,可以发现在安装信息里头多了两行代码(Linux下):
Installing foo script to /usr/local/bin
Installing bar script to /usr/local/bin
查看/usr/local/bin/foo
内容
#!/usr/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'demo==0.1','console_scripts','foo'
__requires__ = 'demo==0.1'
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.exit(
load_entry_point('demo==0.1', 'console_scripts', 'foo')()
)
这个内容其实显示的意思是,foo将执行console_scripts中定义的foo所代表的函数。执行foo,发现打出了hello world!
,和预期结果一样。
使用Eggsecutable Scripts
从字面上来理解这个词,Eggsecutable是Eggs和executable合成词,翻译过来就是另eggs可执行。也就是说定义好一个参数以后,可以另你生成的.egg文件可以被直接执行,貌似Java的.jar也有这机制?不很清楚,下面是使用方法:
setup(
# other arguments here...
entry_points = {
'setuptools.installation': [
'eggsecutable = demo:test',
]
}
)
这么写意味着在执行python *.egg
时,会执行我的test()函数,在文档中说需要将.egg放到PATH路径中。
包含数据文件
在3中我们已经列举了如何包含数据文件,其实setuptools提供的不只这么一种方法,下面是另外两种
1)包含所有包内文件
这种方法中包内所有文件指的是受版本控制(CVS/SVN/GIT等)的文件,或者通过MANIFEST.in声明的
from setuptools import setup, find_packages
setup(
...
include_package_data = True
)
2)包含一部分,排除一部分
from setuptools import setup, find_packages
setup(
...
packages = find_packages('src'),
package_dir = {'':'src'},
include_package_data = True,
# 排除所有 README.txt
exclude_package_data = { '': ['README.txt'] },
)
如果没有使用版本控制的话,可以还是使用3中提到的包含方法
可扩展的框架和应用
setuptools可以帮助你将应用变成插件模式,供别的应用使用。官网举例是一个帮助博客更改输出类型的插件,一个博客可能想要输出不同类型的文章,但是总自己写输出格式化代码太繁琐,可以借助一个已经写好的应用,在编写博客程序的时候动态调用其中的代码。
通过entry_points可以定义一系列接口,供别的应用或者自己调用,例如:
setup(
entry_points = {'blogtool.parsers': '.rst = some_module:SomeClass'}
)
setup(
entry_points = {'blogtool.parsers': ['.rst = some_module:a_func']}
)
setup(
entry_points = """
[blogtool.parsers]
.rst = some.nested.module:SomeClass.some_classmethod [reST]
""",
extras_require = dict(reST = "Docutils>=0.3.5")
)
上面列举了三中定义方式,即我们将我们some_module中的函数,以名字为blogtool.parsers的借口共享给别的应用。
别的应用使用的方法是通过pkg_resources.require()
来导入这些模块。
另外,一个名叫stevedore的库将这个方式做了封装,更加方便进行应用的扩展。
5. 以后增加
以上内容大部分来自于官方文档,需要额外学习的以后再增加