作為Python標準的打包及分發工具,setuptools可以說相當地簡單易用。它會隨著Python一起安裝在你的機器上。你只需寫一個簡短的setup.py安裝檔案,就可以將你的Python應用打包。本文就會介紹下如何編寫安裝檔案及如何打包分發。
首先,如果你需要另外安裝setuptools,你可以使用下面的命令:
1 2 |
$ wget http://peak.telecommunity.com/dist/ez_setup.py $ sudo python ez_setup.py |
第一個安裝檔案
接下來讓我們編寫安裝檔案,假設我們的專案名為setup-demo,包名為myapp,目錄結構如下:
1 2 3 4 5 |
setup-demo/ ├ setup.py # 安裝檔案 └ myapp/ # 原始碼 ├ __init__.py ... |
一個最基本的setup.py檔案如下:
1 2 3 4 5 6 7 8 |
#coding:utf8 from setuptools import setup setup( name='MyApp', # 應用名 version='1.0', # 版本號 packages=['myapp'] # 包括在安裝包內的Python包 ) |
執行安裝檔案
有了上面的setup.py檔案,我們就可以打各種包,也可以將應用安裝在本地Python環境中。
- 建立egg包
1 |
$ python setup.py bdist_egg |
該命令會在當前目錄下的”dist”目錄內建立一個egg檔案,名為”MyApp-1.0-py2.7.egg”。檔名格式就是”應用名-版本號-Python版本.egg”,我本地Python版本是2.7。同時你會注意到,當前目錄多了”build”和”MyApp.egg-info”子目錄來存放打包的中間結果。
- 建立tar.gz包
1 |
$ python setup.py sdist --formats=gztar |
同上例類似,只不過建立的檔案型別是tar.gz,檔名為”MyApp-1.0.tar.gz”。
- 安裝應用
1 |
$ python setup.py install |
該命令會將當前的Python應用安裝到當前Python環境的”site-packages”目錄下,這樣其他程式就可以像匯入標準庫一樣匯入該應用的程式碼了。
- 開發方式安裝
1 |
$ python setup.py develop |
如果應用在開發過程中會頻繁變更,每次安裝還需要先將原來的版本卸掉,很麻煩。使用”develop”開發方式安裝的話,應用程式碼不會真的被拷貝到本地Python環境的”site-packages”目錄下,而是在”site-packages”目錄裡建立一個指向當前應用位置的連結。這樣如果當前位置的原始碼被改動,就會馬上反映到”site-packages”裡。
引入非Python檔案
上例中,我們只會將”myapp”包下的原始碼打包,如果我們還想將其他非Python檔案也打包,比如靜態檔案(JS,CSS,圖片),應該怎麼做呢?這時我們要在專案目錄下新增一個”MANIFEST.in”資料夾。假設我們把所有靜態檔案都放在”static”子目錄下,現在的專案結構如下:
1 2 3 4 5 6 7 |
setup-demo/ ├ setup.py # 安裝檔案 ├ MANIFEST.in # 清單檔案 └ myapp/ # 原始碼 ├ static/ # 靜態檔案目錄 ├ __init__.py ... |
我們在清單檔案”MANIFEST.in”中,列出想要在包內引入的目錄路徑:
1 2 |
recursive-include myapp/static * recursive-include myapp/xxx * |
“recursive-include”表明包含子目錄。別急,還有一件事要做,就是在”setup.py”中將” include_package_data”引數設為True:
1 2 3 4 5 6 7 8 9 |
#coding:utf8 from setuptools import setup setup( name='MyApp', # 應用名 version='1.0', # 版本號 packages=['myapp'], # 包括在安裝包內的Python包 include_package_data=True # 啟用清單檔案MANIFEST.in ) |
之後再次打包或者安裝,”myapp/static”目錄下的所有檔案都會被包含在內。如果你想排除一部分檔案,可以在setup.py中使用”exclude_package_date”引數,比如:
1 2 3 4 5 |
setup( ... include_package_data=True, # 啟用清單檔案MANIFEST.in exclude_package_date={'':['.gitignore']} ) |
上面的程式碼會將所有”.gitignore”檔案排除在包外。如果上述”exclude_package_date”物件屬性不為空,比如”{‘myapp’:[‘.gitignore’]}”,就表明只排除”myapp”包下的所有”.gitignore”檔案。
自動安裝依賴
我們的應用會依賴於第三方的Python包,雖然可以在說明檔案中要求使用者提前安裝依賴包,但畢竟很麻煩,使用者還有可能裝錯版本。其實我們可以在setup.py檔案中指定依賴包,然後在使用setuptools安裝應用時,依賴包的相應版本就會被自動安裝。讓我們來修改上例中的setup.py檔案,加入”install_requires”引數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#coding:utf8 from setuptools import setup setup( name='MyApp', # 應用名 version='1.0', # 版本號 packages=['myapp'], # 包括在安裝包內的Python包 include_package_data=True, # 啟用清單檔案MANIFEST.in exclude_package_date={'':['.gitignore']}, install_requires=[ # 依賴列表 'Flask>=0.10', 'Flask-SQLAlchemy>=1.5,<=2.1' ] ) |
上面的程式碼中,我們宣告瞭應用依賴Flask 0.10及以上版本,和Flask-SQLAlchemy 1.5及以上、2.1及以下版本。setuptools會先檢查本地有沒有符合要求的依賴包,如果沒有的話,就會從PyPI中獲得一個符合條件的最新的包安裝到本地。
大家可以執行下試試,你會發現不但Flask 0.10.1(當前最新版本)被自動安裝了,連Flask的依賴包Jinja2和Werkzeug也被自動安裝了,很方便吧。
如果應用依賴的包無法從PyPI中獲取怎麼辦,我們需要指定其下載路徑:
1 2 3 4 5 6 7 8 9 10 |
setup( ... install_requires=[ # 依賴列表 'Flask>=0.10', 'Flask-SQLAlchemy>=1.5,<=2.1' ], dependency_links=[ # 依賴包下載路徑 'http://example.com/dependency.tar.gz' ] ) |
路徑應指向一個egg包或tar.gz包,也可以是個包含下載地址(一個egg包或tar.gz包)的頁面。個人建議直接指向檔案。
自動搜尋Python包
之前我們在setup.py中指定了”packages=[‘myapp’]”,說明將Python包”myapp”下的原始碼打包。如果我們的應用很大,Python包很多怎麼辦。大家看到這個引數是一個列表,我們當然可以將所有的原始碼包都列在裡面,但肯定很多人覺得這樣做很傻。的確,setuptools提供了”find_packages()”方法來自動搜尋可以引入的Python包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#coding:utf8 from setuptools import setup, find_packages setup( name='MyApp', # 應用名 version='1.0', # 版本號 packages=find_packages(), # 包括在安裝包內的Python包 include_package_data=True, # 啟用清單檔案MANIFEST.in exclude_package_date={'':['.gitignore']}, install_requires=[ # 依賴列表 'Flask>=0.10', 'Flask-SQLAlchemy>=1.5,<=2.1' ] ) |
這樣當前專案內所有的Python包都會自動被搜尋到並引入到打好的包內。”find_packages()”方法可以限定你要搜尋的路徑,比如使用”find_packages(‘src’)”就表明只在”src”子目錄下搜尋所有的Python包。
補充
- zip_safe引數
決定應用是否作為一個zip壓縮後的egg檔案安裝在當前Python環境中,還是作為一個以.egg結尾的目錄安裝在當前環境中。因為有些工具不支援zip壓縮檔案,而且壓縮後的包也不方便除錯,所以建議將其設為False:”zip_safe=False”。
- 描述資訊
部分引數提供了更多當前應用的細節資訊,對打包安裝並無任何影響,比如:
1 2 3 4 5 6 7 8 9 10 |
setup( ... author = "Billy He", author_email = "billy@bjhee.com", description = "This is a sample package", license = "MIT", keywords = "hello world example", url = "http://example.com/HelloWorld/", # 專案主頁 long_description=__doc__, # 從程式碼中獲取文件註釋 ) |
更多內容請參閱setuptools的官方文件。