Python包管理工具setuptools詳解
前言:這篇是別人寫(xiě)的,我這邊只是記錄下,以后查資料的時(shí)候,更方便。
0.什么是setuptools
setuptools是Python distutils增強(qiáng)版的集合,它可以幫助我們更簡(jiǎn)單的創(chuàng)建和分發(fā)Python包,尤其是擁有依賴關(guān)系的。用戶在使用setuptools創(chuàng)建的包時(shí),并不需要已安裝setuptools,只要一個(gè)啟動(dòng)模塊即可。
功能亮點(diǎn):
- 利用EasyInstall自動(dòng)查找、下載、安裝、升級(jí)依賴包
- 創(chuàng)建Python Eggs
- 包含包目錄內(nèi)的數(shù)據(jù)文件
- 自動(dòng)包含包目錄內(nèi)的所有的包,而不用在setup.py中列舉
- 自動(dòng)包含包內(nèi)和發(fā)布有關(guān)的所有相關(guān)文件,而不用創(chuàng)建一個(gè)MANIFEST.in文件
- 自動(dòng)生成經(jīng)過(guò)包裝的腳本或Windows執(zhí)行文件
- 支持Pyrex,即在可以setup.py中列出.pyx文件,而最終用戶無(wú)須安裝Pyrex
- 支持上傳到PyPI
- 可以部署開(kāi)發(fā)模式,使項(xiàng)目在sys.path中
- 用新命令或setup()參數(shù)擴(kuò)展distutils,為多個(gè)項(xiàng)目發(fā)布/重用擴(kuò)展
- 在項(xiàng)目setup()中簡(jiǎn)單聲明entry points,創(chuàng)建可以自動(dòng)發(fā)現(xiàn)擴(kuò)展的應(yīng)用和框架
總之,setuptools就是比distutils好用的多,基本滿足大型項(xiàng)目的安裝和發(fā)布
1.安裝setuptools
1) 最簡(jiǎn)單安裝,假定在ubuntu下
- sudo apt-get install python-setuptools
2) 啟動(dòng)腳本安裝
- wget http://peak.telecommunity.com/dist/ez_setup.py
- sudo python ez_setup.py
2.創(chuàng)建一個(gè)簡(jiǎn)單的包
有了setuptools后,創(chuàng)建一個(gè)包基本上是無(wú)腦操作
cd /tmp
mkdir demo
cd demo
在demo中創(chuàng)建一個(gè)setup.py文件,寫(xiě)入
from setuptools import setup, find_packages
setup(
name = "demo",
version = "0.1",
packages = find_packages(),
)
執(zhí)行python setup.py bdist_egg即可打包一個(gè)test的包了。
demo
|-- build
| `-- bdist.linux-x86_64
|-- demo.egg-info
| |-- dependency_links.txt
| |-- PKG-INFO
| |-- SOURCES.txt
| `-- top_level.txt
|-- dist
| `-- demo-0.1-py2.7.egg
`-- setup.py
在dist中生成的是egg包
file dist/demo-0.1-py2.7.egg
dist/demo-0.1-py2.7.egg: Zip archive data, at least v2.0 to extract
看一下生成的.egg文件,是個(gè)zip包,解開(kāi)看看先
upzip -l dist/demo-0.1-py2.7.egg
Archive: dist/demo-0.1-py2.7.egg
Length Date Time Name
--------- ---------- ----- ----
1 2013-06-07 22:03 EGG-INFO/dependency_links.txt
1 2013-06-07 22:03 EGG-INFO/zip-safe
120 2013-06-07 22:03 EGG-INFO/SOURCES.txt
1 2013-06-07 22:03 EGG-INFO/top_level.txt
176 2013-06-07 22:03 EGG-INFO/PKG-INFO
--------- -------
299 5 files
我們可以看到,里面是一系列自動(dòng)生成的文件。現(xiàn)在可以介紹一下剛剛setup()中的參數(shù)了
name 包名
version 版本號(hào)
packages 所包含的其他包
要想發(fā)布到PyPI中,需要增加別的參數(shù),這個(gè)可以參考官方文檔中的例子了。
3.給包增加內(nèi)容
上面生成的egg中沒(méi)有實(shí)質(zhì)的內(nèi)容,顯然誰(shuí)也用不了,現(xiàn)在我們稍微調(diào)色一下,增加一點(diǎn)內(nèi)容。
在demo中執(zhí)行mkdir demo,再創(chuàng)建一個(gè)目錄,在這個(gè)demo目錄中創(chuàng)建一個(gè)init.py的文件,表示這個(gè)目錄是一個(gè)包,然后寫(xiě)入:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
def test():
print "hello world!"
if __name__ == '__main__':
test()
現(xiàn)在的主目錄結(jié)構(gòu)為下:
demo
|-- demo
| `-- __init__.py
`-- setup.py
再次執(zhí)行python setup.py bdist_egg后,再看egg包
Archive: dist/demo-0.1-py2.7.egg
Length Date Time Name
--------- ---------- ----- ----
1 2013-06-07 22:23 EGG-INFO/dependency_links.txt
1 2013-06-07 22:23 EGG-INFO/zip-safe
137 2013-06-07 22:23 EGG-INFO/SOURCES.txt
5 2013-06-07 22:23 EGG-INFO/top_level.txt
176 2013-06-07 22:23 EGG-INFO/PKG-INFO
95 2013-06-07 22:21 demo/__init__.py
338 2013-06-07 22:23 demo/__init__.pyc
--------- -------
753 7 files
這回包內(nèi)多了demo目錄,顯然已經(jīng)有了我們自己的東西了,安裝體驗(yàn)一下。
python setup.py install
這個(gè)命令會(huì)講我們創(chuàng)建的egg安裝到python的dist-packages目錄下,我這里的位置在
tree /usr/local/lib/python2.7/dist-packages/demo-0.1-py2.7.egg
查看一下它的結(jié)構(gòu):
/usr/local/lib/python2.7/dist-packages/demo-0.1-py2.7.egg
|-- demo
| |-- __init__.py
| `-- __init__.pyc
`-- EGG-INFO
|-- dependency_links.txt
|-- PKG-INFO
|-- SOURCES.txt
|-- top_level.txt
`-- zip-safe
打開(kāi)python終端或者ipython都行,直接導(dǎo)入我們的包
>>> import demo
>>> demo.test()
hello world!
>>>
好了,執(zhí)行成功!
4.setuptools進(jìn)階
在上例中,在前兩例中,我們基本都使用setup()的默認(rèn)參數(shù),這只能寫(xiě)一些簡(jiǎn)單的egg。一旦我們的project逐漸變大以后,維護(hù)起來(lái)就有點(diǎn)復(fù)雜了,下面是setup()的其他參數(shù),我們可以學(xué)習(xí)一下
使用find_packages()
對(duì)于簡(jiǎn)單工程來(lái)說(shuō),手動(dòng)增加packages參數(shù)很容易,剛剛我們用到了這個(gè)函數(shù),它默認(rèn)在和setup.py同一目錄下搜索各個(gè)含有init.py的包。其實(shí)我們可以將包統(tǒng)一放在一個(gè)src目錄中,另外,這個(gè)包內(nèi)可能還有aaa.txt文件和data數(shù)據(jù)文件夾。
demo
├── setup.py
└── src
└── demo
├── __init__.py
├── aaa.txt
└── data
├── abc.dat
└── abcd.dat
如果不加控制,則setuptools只會(huì)將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中就包含了所需文件了??纯矗?/p>
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中再增加一個(gè)tests包,可以通過(guò)exclude來(lái)排除它,
find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"])
使用entry_points
一個(gè)字典,從entry point組名映射道一個(gè)表示entry point的字符串或字符串列表。Entry points是用來(lái)支持動(dòng)態(tài)發(fā)現(xiàn)服務(wù)和插件的,也用來(lái)支持自動(dòng)生成腳本。這個(gè)還是看例子比較好理解:
setup(
entry_points = {
'console_scripts': [
'foo = demo:test',
'bar = demo:test',
],
'gui_scripts': [
'baz = demo:test',
]
}
)
修改setup.py增加以上內(nèi)容以后,再次安裝這個(gè)egg,可以發(fā)現(xiàn)在安裝信息里頭多了兩行代碼(Linux下):
Installing foo script to /usr/local/bin
Installing bar script to /usr/local/bin
查看/usr/local/bin/foo內(nèi)容
#!/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')()
)
這個(gè)內(nèi)容其實(shí)顯示的意思是,foo將執(zhí)行console_scripts中定義的foo所代表的函數(shù)。執(zhí)行foo,發(fā)現(xiàn)打出了hello world!,和預(yù)期結(jié)果一樣。
使用Eggsecutable Scripts
從字面上來(lái)理解這個(gè)詞,Eggsecutable是Eggs和executable合成詞,翻譯過(guò)來(lái)就是另eggs可執(zhí)行。也就是說(shuō)定義好一個(gè)參數(shù)以后,可以另你生成的.egg文件可以被直接執(zhí)行,貌似Java的.jar也有這機(jī)制?不很清楚,下面是使用方法:
setup(
# other arguments here...
entry_points = {
'setuptools.installation': [
'eggsecutable = demo:test',
]
}
)
這么寫(xiě)意味著在執(zhí)行python *.egg時(shí),會(huì)執(zhí)行我的test()函數(shù),在文檔中說(shuō)需要將.egg放到PATH路徑中。
包含數(shù)據(jù)文件
在3中我們已經(jīng)列舉了如何包含數(shù)據(jù)文件,其實(shí)setuptools提供的不只這么一種方法,下面是另外兩種
1)包含所有包內(nèi)文件
這種方法中包內(nèi)所有文件指的是受版本控制(CVS/SVN/GIT等)的文件,或者通過(guò)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'] },
)
如果沒(méi)有使用版本控制的話,可以還是使用3中提到的包含方法
可擴(kuò)展的框架和應(yīng)用
setuptools可以幫助你將應(yīng)用變成插件模式,供別的應(yīng)用使用。官網(wǎng)舉例是一個(gè)幫助博客更改輸出類型的插件,一個(gè)博客可能想要輸出不同類型的文章,但是總自己寫(xiě)輸出格式化代碼太繁瑣,可以借助一個(gè)已經(jīng)寫(xiě)好的應(yīng)用,在編寫(xiě)博客程序的時(shí)候動(dòng)態(tài)調(diào)用其中的代碼。
通過(guò)entry_points可以定義一系列接口,供別的應(yīng)用或者自己調(diào)用,例如:
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中的函數(shù),以名字為blogtool.parsers的借口共享給別的應(yīng)用。
別的應(yīng)用使用的方法是通過(guò)pkg_resources.require()來(lái)導(dǎo)入這些模塊。
另外,一個(gè)名叫stevedore的庫(kù)將這個(gè)方式做了封裝,更加方便進(jìn)行應(yīng)用的擴(kuò)展。