Pyinstaller打包通用流程

Pyinstaller打包通用流程

前言

什麼是Pyinstaller

Pyinstaller是用於打包python項目的一個工具, 能夠將項目代碼打包成可執行文件, 在其餘機器上使用.
通俗的說, 沒打包的時候運行程序的命令是:python3 main.py arg1 arg2 ....那麼打包完後能夠這麼執行./main arg1 arg2 ..., main是你打包後的可執行文件名. arg1 arg2 ...就是運行程序的參數,能夠是sys.argv, argparser或者其它命令行參數工具.python

就我的使用狀況來看,Pyinstaller有兩大特色(未必是優勢):git

  1. 部署方便, 目標機器能夠沒有各類繁雜的第三方python庫, 甚至不須要python環境,你帶着一個可執行程序就能夠去部署了.
  2. 代碼安全, 帶着可執行程序去運行能夠避免代碼泄露, 可是若是要徹底保護代碼還要考慮防反編譯.

我爲何寫這篇文章

就官方教程和網上數以萬計的博客來看, 打包流程真的很簡單, 首先把你的祖傳代碼改好並肯定好入口文件(這裏假設是main.py),而後執行命令pyinstaller -F main.py 最後去dist目錄拿可執行文件main(windows下多是exe後綴)就能夠愉快運行了.github

可是在這個簡單的過程當中,確總會出各類錯誤,其中主要緣由是使用Pyinstaller的場景每每都是工業界部署, 須要打包一個項目或工程, 項目複雜必然容易出錯.docker

本人自參加工做以來,常常參與打包部署, 遇到了不少坑, 所以專門寫了這篇文章來分享靠譜的打包流程和常見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的錯誤windows

Step2 配置打包項

一般狀況下寫好代碼,裝好必備庫基本就能夠執行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,hiddenimportshookspath. 它們的主要功能我都加在了註釋裏, 下面我會詳細講述這個四個選項應該如何配置,這也是本文的重要內容之一.安全

(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 foundimport error的錯誤,基本經過hiddenimports,hooksbinaries解決.(這個錯誤基本佔了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裏打包試試

最後感謝各位閱讀, 但願能幫到大家.

文章能夠轉載, 但請註明出處:

相關文章
相關標籤/搜索