Pyinstaller是用於打包python項目的一個工具, 能夠將項目代碼打包成可執行文件, 在其餘機器上使用.
通俗的說, 沒打包的時候運行程序的命令是:python3 main.py arg1 arg2 ...
.那麼打包完後能夠這麼執行./main arg1 arg2 ...
, main是你打包後的可執行文件名. arg1 arg2 ...
就是運行程序的參數,能夠是sys.argv
, argparser
或者其它命令行參數工具.python
就我的使用狀況來看,Pyinstaller有兩大特色(未必是優勢):git
就官方教程和網上數以萬計的博客來看, 打包流程真的很簡單, 首先把你的祖傳代碼改好並肯定好入口文件(這裏假設是main.py
),而後執行命令pyinstaller -F main.py
最後去dist
目錄拿可執行文件main(windows下多是exe後綴)
就能夠愉快運行了.github
可是在這個簡單的過程當中,確總會出各類錯誤,其中主要緣由是使用Pyinstaller的場景每每都是工業界部署, 須要打包一個項目或工程, 項目複雜必然容易出錯.docker
本人自參加工做以來,常常參與打包部署, 遇到了不少坑, 所以專門寫了這篇文章來分享靠譜的打包流程和常見Bug解決辦法. 本人主要工做環境是Linux, 所以本文對Linux下的打包更具指導意義.編程
本文主要分紅兩部分,第一部分講通用靠譜的打包流程, 這是我無數次打包總結的一套流程, 但願能幫助到你們.
第二部介紹Pyinstaller打包常見Bug和相關Tips.小程序
打包環境須要安裝pyinstaller庫和setuptools庫, pip install
便可.
這裏強烈建議使用 pyinstaller 3.5版本和setuptools 44.0版本,不然有必定概率會出現bug.
pyinstaller高版本可能會在你使用hooks時報錯.
setuptools高版本可能會在你打包過程當中出現pkg_resources.py2_warn
的錯誤windows
一般狀況下寫好代碼,裝好必備庫基本就能夠執行pyinstaller -F main.py
了.可是考慮到項目複雜要作不少配置, 咱們先來生成一個打包配置文件, 執行命令pyi-makespec -F main.py
, 而後你就會在main.py的同級目錄下看到main.spec文件. 這個文件的主要做用就是指定打包的各類配置, 下面貼一份一個spec文件內容:api
# -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis(['main.py'], pathex=['/home'], binaries=[], # 打包動態連接庫文件(so或dll) datas=[], # 打包程序須要的數據(文本\音視頻等) hiddenimports=[], # 一些難以打包進去的庫放到裏面(一般是複雜的庫) hookspath=[], # 指定hook文件夾,可以搜索添加庫所需的全部文件 runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE(pyz, a.scripts, [], exclude_binaries=True, name='main', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=True ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='main')
spec文件內容仍是挺多的,語法就是python,所以修改該文件的時候請遵循python語法. 內容雖多,不過真正經常使用的就四項分別是 binaries
,datas
,hiddenimports
和hookspath
. 它們的主要功能我都加在了註釋裏, 下面我會詳細講述這個四個選項應該如何配置,這也是本文的重要內容之一.安全
binaries用於添加程序運行時所需的一些動態連接庫文件,舉個例子你打包後的程序報錯說找不到xxx.so
文件,那麼你能夠先經過find命令找到這個文件地址而後把它放到binaries
列表裏面:函數
binaries=['/home/xx/xxx/xx.so']
若是你的打包程序須要播放好多音頻文件(假設都在resources
目錄內), 那你就能夠把這個音頻文件夾地址放到datas
列表中,格式以下:
datas = [('./resources/','songs')]
除了指定音頻文件目錄,咱們還加了一個字符串songs
,這是表示你的可執行程序釋放文件時把resources
文件夾裏的內容釋放到songs
文件夾裏.一般狀況下執行可執行程序時, 程序會在/tmp
目錄創建一個_MEI
開頭臨時目錄,並將程序運行所需文件都釋放到這裏,因此你全部的音頻文件均可以在這個臨時目錄裏的songs
文件夾找到.
寫到這裏各位應該會發現一個關鍵點: 你的代碼須要判斷是本身是如何被執行的, 經過打包後的可執行程序執行的?仍是經過main.py執行的?, 那否則無法肯定資源訪問目錄, 解決辦法: 若是getattr(sys,"frzozen",False)
爲真,則是在可執行文件裏執行的,而後經過sys._MEIPASS
獲取上文提到的臨時文件目錄.
最後,本人不建議將數據打包到程序中,代碼和數據分離是編程基本原則,數據加在可執行程序裏,不只讓程序體積變大,還會使修改\替換數據變的複雜(具體修改方法取決於你的代碼加載文件機制,一個騷操做是趁程序釋放文件之時,迅速定位目錄位置進行文件替換>~<), 因此能分離就分離吧!
若是你的程序報xxx模塊找不到的錯誤,那麼每每就是某個程序隱式調用了該模塊,使得pyinstaller沒有掃描到. 這個時候咱們就把他顯示加進去,舉個例子,程序報錯找不到numpy庫,那麼咱們就作以下操做:
hiddenimports=['numpy']
有時候不只要把xx庫加進去,還要把xx.yy加進去,好比 hiddenimports=['tensorflow','tensorflow.contrib']
,
具體根據報錯信息來肯定
hookspath的做用是搜索某個庫所需的文件並把他們添加到打包程序裏. hookspath指定一個文件夾路徑(一般狀況下文件夾名就叫hooks),hooks文件夾裏有若干python文件,以hook-庫名格式命名,好比對於gevent, 就要命名爲hook-gevent.py. 強烈推薦文件內容這麼寫:
from PyInstaller.utils.hooks import collect_all def hook(hook_api): packages = ['gevent'] for package in packages: datas, binaries, hiddenimports = collect_all(package) # hook_api.add_datas(datas) # 註釋掉是由於一般用不到 # hook_api.add_binaries(binaries) hook_api.add_imports(*hiddenimports)
簡單解釋下,藉助collect_all
函數能夠分析出這個庫須要的全部文件和庫, 以gevent爲例, 執行
datas, binaries, hiddenimports = collect_all('gevent')
那麼:
__greenlet_primitives.pxd
, __hub_local.pxd
等__greenlet_primitives.cp37-win_amd64.pyd
等gevent.threadpool
, gevent._semaphore
等而後藉助上面代碼中的hook_api.add_xxx
函數把他們添加進去,我註釋掉了兩行,是由於有時候這兩行不須要,實戰時能夠根據報錯按需添加.
能夠看出hooks可替代hiddenimports, 但仍是建議優先使用hiddenimports, 這也是爲了減少程序體積.
若是你把上述配置都作好了,那麼恭喜你,基本上打包和運行就很難出錯了.
打包命令是pyinstaller -F main.spec
. 注意是spec文件, 不是py文件
而後去dist
目錄裏找到main
文件,最後./main args
測試便可.
另外附上被打包項目的目錄結構:
ProjectName │ main.py # 入口文件 │ main.spec # 配置文件 │ ├─codes # 你的祖傳代碼 └─hooks # hook文件 └─────hook-gevent.py └─────hook-tensorflow.py
下面是本人打包時遇到的一些bug和解決思路以及我的打包經驗, 供你們參考:
not found
或import error
的錯誤,基本經過hiddenimports
,hooks
和binaries
解決.(這個錯誤基本佔了Pyinstlaller全部報錯的 90%....)struct.error: 'i' format requires -2147483648 <= number <= 2147483647
是指你的打包文件太大了,超出2GB限制了,詳情看這個issue. 解決方法,要麼精簡模塊,要麼慎用hooks功能,別把啥東西都往程序裏塞,這也是建議優先hiddenimports的緣由.Qt failed
的相關信息不用理會,和python的圖形界面編程有關最後感謝各位閱讀, 但願能幫到大家.
文章能夠轉載, 但請註明出處: