Pyinstaller打包通用流程
Pyinstaller打包通用流程
前言
什麼是Pyinstaller
Pyinstaller
是用於打包python專案的一個工具, 可以將專案程式碼打包成可執行檔案, 在其他機器上使用。 通俗的說, 沒打包的時候執行程式的命令是:
python3 main。py arg1 arg2 。。。
。那麼打包完後可以這麼執行
。/main arg1 arg2 。。。
, main是你打包後的可執行檔名。
arg1 arg2 。。。
就是執行程式的引數,可以是
sys。argv
,
argparser
或者其它命令列引數工具。
就個人使用情況來看,
Pyinstaller
有兩大特點(未必是優點):
部署方便, 目標機器可以沒有各種繁雜的第三方python庫, 甚至不需要python環境,你帶著一個可執行程式就可以去部署了。
程式碼安全, 帶著可執行程式去執行可以避免程式碼洩露, 但是如果要完全保護程式碼還要考慮防反編譯。
我為什麼寫這篇文章
就官方教程和網上數以萬計的部落格來看, 打包流程真的很簡單, 首先把你的祖傳程式碼改好並確定好入口檔案(這裡假設是
main。py
),然後執行命令
pyinstaller -F main。py
最後去
dist
目錄拿可執行檔案
main(windows下可能是exe字尾)
就可以愉快運行了。
但是在這個簡單的過程中,確總會出各種錯誤,其中主要原因是使用
Pyinstaller
的場景往往都是工業界部署, 需要打包一個專案或工程, 專案複雜必然容易出錯。
本人自參加工作以來,經常參與打包部署, 遇到了很多坑, 因此專門寫了這篇文章來分享靠譜的打包流程和常見Bug解決辦法。 本人主要工作環境是Linux, 因此本文對Linux下的打包更具指導意義。
本文主要內容
本文主要分成兩部分,第一部分講通用靠譜的打包流程, 這是我無數次打包總結的一套流程, 希望能幫助到大家。 第二部介紹
Pyinstaller
打包常見Bug和相關Tips。
pyinstaller打包的流程
Step1 工具準備
打包環境需要安裝
pyinstaller
庫和
setuptools
庫,
pip install
即可。 這裡強烈建議使用
pyinstaller 3.5
版本和
setuptools 44.0
版本,否則有一定機率會出現bug。
pyinstaller
高版本可能會在你使用hooks時報錯。
setuptools
高版本可能會在你打包過程中出現
pkg_resources。py2_warn
的錯誤
Step2 配置打包項
通常情況下寫好程式碼,裝好必備庫基本就可以執行
pyinstaller -F main。py
了。但是考慮到專案複雜要做很多配置, 我們先來生成一個打包配置檔案, 執行命令
pyi-makespec -F main。py
, 然後你就會在
main.py
的同級目錄下看到
main.spec
檔案。 這個檔案的主要作用就是指定打包的各種配置, 下面貼一份一個spec檔案內容:
# -*- 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
。 它們的主要功能我都加在了註釋裡, 下面我會詳細講述這個四個選項應該如何配置,這也是本文的重要內容之一。
(1) binaries
binaries
用於新增程式執行時所需的一些動態連結庫檔案,舉個例子你打包後的程式報錯說找不到
xxx。so
檔案,那麼你可以先透過
find
命令找到這個檔案地址然後把它放到
binaries
列表裡面:
binaries=[‘/home/xx/xxx/xx。so’]
(2) datas
如果你的打包程式需要播放好多音訊檔案(假設都在
resources
目錄內), 那你就可以把這個音訊資料夾地址放到
datas
列表中,格式如下:
datas = [(‘。/resources/’,‘songs’)]
除了指定音訊檔案目錄,我們還加了一個字串
songs
,這是表示你的可執行程式釋放檔案時把
resources
資料夾裡的內容釋放到
songs
資料夾裡。通常情況下執行可執行程式時, 程式會在
/tmp
目錄建立一個
_MEI
開頭臨時目錄,並將程式執行所需檔案都釋放到這裡,所以你所有的音訊檔案都可以在這個臨時目錄裡的
songs
資料夾找到。
寫到這裡各位應該會發現一個關鍵點: 你的程式碼需要判斷是自己是如何被執行的, 透過打包後的可執行程式執行的?還是透過
main.py
執行的?, 那不然沒法確定資源訪問目錄, 解決辦法: 如果
getattr(sys,“frzozen”,False)
為真,則是在可執行檔案裡執行的,然後透過
sys。_MEIPASS
獲取上文提到的臨時檔案目錄。
最後,本人
不建議將資料打包到程式中
,程式碼和資料分離是程式設計基本原則,資料加在可執行程式裡,不僅讓程式體積變大,還會使修改\替換資料變的複雜(具體修改方法取決於你的程式碼載入檔案機制,一個騷操作是趁程式釋放檔案之時,迅速定位目錄位置進行檔案替換>~<), 所以能分離就分離吧!
(3) hiddenimports
如果你的程式報xxx模組找不到的錯誤,那麼往往就是某個程式隱式呼叫了該模組,使得pyinstaller沒有掃描到。 這個時候我們就把他顯示加進去,舉個例子,程式報錯找不到numpy庫,那麼我們就做如下操作:
hiddenimports=[‘numpy’]
有時候不僅要把xx庫加進去,還要把xx。yy加進去,比如
hiddenimports=[‘tensorflow’,‘tensorflow。contrib’]
, 具體根據報錯資訊來確定
(4) hookspath
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
, 這也是為了減小程式體積。
Step3 打包&測試
如果你把上述配置都做好了,那麼恭喜你,基本上打包和執行就很難出錯了。 打包命令是
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和相關Tips
下面是本人打包時遇到的一些bug和解決思路以及個人打包經驗, 供大家參考:
遇到各種
not found
或
import error
的錯誤,基本透過
hiddenimports
,
hooks
和
binaries
解決。(這個錯誤基本佔了
Pyinstlaller
所有報錯的
90%
。。。。)
遇到這種罕見錯誤:
struct。error: ‘i’ format requires -2147483648 <= number <= 2147483647
是指你的打包檔案太大了,超出2GB限制了,詳情看這個issue。 解決方法,要麼精簡模組,要麼慎用
hooks
功能,別把啥東西都往程式裡塞,這也是建議優先
hiddenimports
的原因。
能用
hiddenimports
就別用
hooks
,減小體積
打包程式多輸出除錯資訊方便定位bug。
打包前務必認真測試,打包後出了bug就要重新打包了
打包深度學習程式等的時間在十分鐘左右,保持耐心
tensorflow最好1。14,實測大於1。14會出問題
打包時會有
Qt failed
的相關資訊不用理會,和python的圖形介面程式設計有關
Pyinstaller
程式使用GPU務必保證環境變數設定的沒問題
同樣是
Pyinstaller
程式使用GPU的問題,打包環境和執行環境顯示卡驅動版本最好一致,那不然有可能用不了GPU(這個本人還未充分驗證),如果真遇到了這個情況,歡迎使用
nvdocker
。
如果一些bug怎麼都解決不了,嘗試切換不同庫版本(比如pyinstaller,setuptools和你的程式使用的庫), 甚至在純淨的docker裡打包試試
最後感謝各位閱讀, 希望能幫到你們。
文章可以轉載, 但請註明出處:
-
-
-
-
上一篇:第四十九節 搓餌返水
下一篇:螞蟻有沒有聽覺?