打包的好處:不需要安裝python即可直接運作,将py程式和依賴包打到一個二進制檔案裡,不需要安裝任何依賴,支援多平台,适合分發;
常用的打包工具:
pyinstaller,cx_Freeze(支援多平台)
py2exe,pynsist(隻支援win平台)
py2app(支援macos平台)
bbFreeze(隻支援win,linux平台上的py2版本)
osnap(隻支援win和mac平台)
打包工具官方網站及狀态
http://www.pyinstaller.org/ # 持續更新中
https://cx-freeze.readthedocs.io/en/latest/ # 持續更新中
https://github.com/py2exe/py2exe # 近3個月未更新
https://pynsist.readthedocs.io/en/latest/ # 近2個月未更新
https://github.com/ronaldoussoren/py2pp # 持續更新中
https://github.com/schmir/bbfreeze # 廢棄
https://github.com/jamesabel/osnap # 廢棄
pyinstaller介紹
是一個跨平台的打包程式,目前已支援win|linux|macos|freebsd|solaris|aix等多個平台的可執行程式打包,配置簡單;
pip install pyinstaller # python3.6+
pyinstaller --version # 4.5.1
vim pyinstallerDemo.py
pyinstaller -D ./pyinstallerDemo.py # dist/pyinstallDemo/pyinstallerDemo.exe,單目錄(多檔案)格式;若代碼中有引入外部檔案如base_dir=os.path.dirname(os.path.abspath(__file__)),在多檔案這種形式下是可以正常運作的,但在“單檔案格式”中會報錯(運作時會在使用者家目錄下建立臨時目錄,運作結束會删除臨時目錄),要用base_dir=os.path.dirname(os.path.realpath(sys.argv[0]))
pyinstaller -F ./pyinstallerDemo.py # 單檔案格式;如果在運作時需要檢視程式中的異常資訊,可先打開win console再輸入腳本的完整路徑 - 再運作,而輕按兩下打開的console在異常時會直接退出
-n NAME.exe # 自定義目标可執行檔案名
yum -y install python38-devel # linux平台
注,目标目錄下檔案名和目錄名不能相同,否則會報錯
pyinstaller原理:
查找檔案-查找子產品,在腳本裡找所有import語句,然後根據import語句後面的子產品名找到對應的子產品位置,再疊代查找,直到找到所有需要的子產品;
查找檔案-查找py解釋器,找到你的開發環境設定的py解釋器路徑,然後将py解釋器檔案及所需的依賴檔案都複制過來;
查找檔案-查找時需要注意的問題,pyinstaller是根據import語句來查找子產品,而在py中除了import語句以外,還有另外的方式可導入子產品:__import__()函數、importlib.import_module(),如果遇到這種情況會看到報錯No module found,解決辦法:改為import語句導入子產品|在打包時通過指令行選項指定子產品路徑|編輯spec檔案告訴pyinstaller需要額外導入哪些子產品;
兩種格式:
單目錄格式,預設,适合大型項目,即把所有的檔案和依賴打包到一個目錄裡去,然後在目錄下可執行打包好的exe檔案,這種模式有2個優點(非常适合排錯,因為在目錄裡你可以清楚的看到包含了哪些子產品;在更新代碼時,隻需要更新重新打包後的exe檔案,而單目錄格式下這個檔案非常小,是以在大規模分發時非常有優勢);劣勢(在大量依賴中找到需要的那個可執行檔案比較麻煩;不小心會删掉目錄中的任意檔案會導緻無法運作);
單檔案格式,适合腳本或小項目,是把所有依賴和py腳本打包到一個exe檔案裡,實際使用時直接輕按兩下這個exe檔案或/path/xxx.exe調用,單檔案模式執行時,它首先會在機器上建立一個臨時目錄,然後使用内部打包的py解釋器在這個臨時目錄下啟動一個臨時py環境,在這個環境裡執行py腳本,同時在打包的檔案内部查找打包進來的子產品,還會查找需要的檔案,有時還需要解壓打包的檔案,是以單個檔案模式可能會比單目錄格式要慢些;優勢(隻有單個可執行檔案、使用簡單友善;不擔心删除單個檔案導緻無法執行的情況);劣勢(單個打封包件在依賴多時,會非常大,需要使用壓縮措施;建立的臨時目錄在程式被殺死時不會自動删除,是以在應用頻繁崩潰時,會大量消耗硬碟空間,需要手動清理);
使用spec檔案:
在單目錄格式或單檔案格式,都會生成一個.spec檔案,這個檔案是用來告訴pyinstaller怎麼處理你的腳本,pyinstaller通過執行這個spec檔案裡的内容來建構app,可了解為linux裡打rpm包時使用的spec檔案;
在大部分場景下,基本上都不需要主動編寫或修改這個檔案,實際應用場景有:需要在app裡打包資料檔案(如包含sqlite資料庫檔案);需要從其他位置包含.dll或.so檔案;需要添加py運作時參數到可執行檔案裡;
注,單檔案格式使用外部檔案,官方提供解決方案:
if getattr(sys, 'frozen', False):
base_dir = os.path.dirname(sys.executable)
else:
base_dir = os.path.dirname(os.path.abspath(__file__))
注,打包時-導入子產品問題:
正常的from utils import db導入沒問題,db.get_conn();
但如果是動态導入子產品的代碼時,是無法找到關聯的包,如
import importlib
db = importlib.import_module('utils.db')
db.get_conn()
解決辦法:
a = Analysis( # 在.spec檔案中指定,并在打包時需指定.spec檔案如pyinstaller -F demo.spec
hiddenimports=[
'utils.db',
],
)
注,在使用多線程時,win平台下需在__main__()的第一行加上:
multiprocessing.freeze_support()