Setuptools 【Python工具包詳解】
什麼是setuptools
setuptools是Python distutils增強版的集合,它可以幫助我們更簡單的建立和分發Python包,尤其是擁有依賴關係的。使用者在使用setuptools建立的包時,並不需要已安裝setuptools,只要一個啟動模組即可。功能亮點
- 利用EasyInstall自動查詢、下載、安裝、升級依賴包
- 建立Python Eggs
- 包含包目錄內的資料檔案
- 自動包含包目錄內的所有的包,而不用在setup.py中列舉
- 自動包含包內和釋出有關的所有相關檔案,而不用建立一個MANIFEST.in檔案
- 自動生成經過包裝的指令碼或Windows執行檔案
- 支援Pyrex,即在可以setup.py中列出.pyx檔案,而終端使用者無須安裝Pyrex
- 支援上傳到PyPI
- 可以部署開發模式,使專案在sys.path中
- 用新命令或setup()引數擴充套件distutils,為多個專案釋出/重用擴充套件
- 在專案setup()中簡單宣告entry points,建立可以自動發現擴充套件的應用和框架
- 總之,setuptools就是比distutils好用的多,基本滿足大型專案的安裝和釋出
安裝setuptools
1) 最簡單安裝,假定在ubuntu下
# sudo apt-get install python-setuptools
2) 啟動指令碼安裝
wget http://peak.telecommunity.com/dist/ez_setup.pysudo python ez_setup.py
建立一個簡單的包
有了setuptools後,建立一個包基本上是無腦操作
cd /tmp
mkdir demo
cd demo
在demo中建立一個setup.py檔案,寫入
from setuptools import setup, find_packages setup( name = "demo", version = "0.1", packages = find_packages(), )
執行python setup.py bdist_egg即可打包一個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檔案,是個zip包,解開看看先
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
我們可以看到,裡面是一系列自動生成的檔案。現在可以介紹一下剛剛setup()中的引數了
name 包名
version 版本號
packages 所包含的其他包
要想釋出到PyPI中,需要增加別的引數,這個可以參考官方文件中的例子了。
給包增加內容
上面生成的egg中沒有實質的內容,顯然誰也用不了,現在我們稍微調色一下,增加一點內容。
在demo中執行mkdir demo,再建立一個目錄,在這個demo目錄中建立一個init.py的檔案,表示這個目錄是一個包,然後寫入:
#!/usr/bin/env python #-*- coding:utf-8 -*- def test(): print "hello world!" if __name__ == '__main__': test()
現在的主目錄結構為下:
demo |-- demo | `-- __init__.py `-- setup.py 再次執行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
這回包內多了demo目錄,顯然已經有了我們自己的東西了,安裝體驗一下。
python setup.py install
這個命令會講我們建立的egg安裝到python的dist-packages目錄下,我這裡的位置在
tree /usr/local/lib/python2.7/dist-packages/demo-0.1-py2.7.egg
檢視一下它的結構:
/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
開啟python終端或者ipython都行,直接匯入我們的包
>>> import demo >>> demo.test() hello world! >>> # 好了,執行成功!
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的庫將這個方式做了封裝,更加方便進行應用的擴充套件。
關注、留言,我們一起學習,您的收藏是我持續更新的動力!
===============Talk is cheap, show me the code,bye-bye================