您當前的位置:首頁 > 體育

Pyinstaller打包通用流程

作者:由 infgrad 發表于 體育時間:2020-07-31

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裡打包試試

最後感謝各位閱讀, 希望能幫到你們。

文章可以轉載, 但請註明出處:

-

-

-

-

標簽: 打包  main  檔案  py  hiddenimports