使用Pyinstaller打包Python項(xiàng)目包含了大量的坑,這篇文章總結(jié)實(shí)踐得到的Pyinstaller打包經(jīng)驗(yàn)。本文的例子為Python3.6代碼,Pyinstaller3.4,在windows下打包為64位和32位版本。
目錄
Pyinstaller基本使用方法
Python項(xiàng)目的打包方法
1.spec文件生成
2.spec文件配置
3.使用spec執(zhí)行打包命令
Python模塊的打包問題
凍結(jié)打包路徑
其它問題
Pyinstaller基本使用方法
Pyinstaller可以通過簡(jiǎn)單的命令進(jìn)行python代碼的打包工作,其基本的命令為:
pyinstaller -option xxx.py
options的詳情可參考官方幫助文檔https://pyinstaller.readthedocs.io/en/stable/usage.html
這邊只介紹用到的option:-d生成一個(gè)文件目錄包含可執(zhí)行文件和相關(guān)動(dòng)態(tài)鏈接庫(kù)和資源文件等;-f僅生成一個(gè)可執(zhí)行文件
-D, --onedirCreate a one-folder bundle containing an executable (default)
-F, --onefileCreate a one-file bundled executable.
對(duì)于打包結(jié)果較大的項(xiàng)目,選用-d生成目錄相比單可執(zhí)行文件的打包方式,執(zhí)行速度更快,但包含更加多的文件。本文的例子選中-d方式打包。
Python項(xiàng)目的打包方法
以一個(gè)多文件和目錄的Python項(xiàng)目為例,項(xiàng)目文件包含:1.Python源代碼文件;2.圖標(biāo)資源文件;3.其它資源文件
以圖中項(xiàng)目為例,Python源代碼文件在多個(gè)目錄下:bin, lib\app, lib\models, lib\views;圖標(biāo)資源文件在lib\icon目錄下;其它資源文件在data目錄下,包括文本文件,視頻文件等等。
1.spec文件生成
為了進(jìn)行自定義配置的打包,首先需要編寫打包的配置文件.spec文件。當(dāng)使用pyinstaller -d xxx.py時(shí)候會(huì)生成默認(rèn)的xxx.spec文件進(jìn)行默認(rèn)的打包配置。通過配置spec腳本,并執(zhí)行pyinstaller -d xxx.spec完成自定義的打包。
通過生成spec文件的命令,針對(duì)代碼的主程序文件生成打包對(duì)應(yīng)的spec文件
pyi-makespec -w xxx.py
打開生成的spec文件,修改其默認(rèn)腳本,完成自定義打包需要的配置。spec文件是一個(gè)python腳本,其默認(rèn)的結(jié)構(gòu)如下例所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | # -*- mode: python -*- block_cipher = None a = Analysis([ 'fastplot.py' ], pathex = [ 'D:\\install_test\\DAGUI-0.1\\bin' ], binaries = [], datas = [], hiddenimports = [], hookspath = [], runtime_hooks = [], excludes = [], win_no_prefer_redirects = False , win_private_assemblies = False , cipher = block_cipher) pyz = PYZ(a.pure, a.zipped_data, cipher = block_cipher) exe = EXE(pyz, a.scripts, exclude_binaries = True , name = 'fastplot' , debug = False , strip = False , upx = True , console = False ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip = False , upx = True , name = 'fastplot' ) |
spec文件中主要包含4個(gè)class: Analysis, PYZ, EXE和COLLECT.
Analysis以py文件為輸入,它會(huì)分析py文件的依賴模塊,并生成相應(yīng)的信息
PYZ是一個(gè).pyz的壓縮包,包含程序運(yùn)行需要的所有依賴
EXE根據(jù)上面兩項(xiàng)生成
COLLECT生成其他部分的輸出文件夾,COLLECT也可以沒有
2.spec文件配置
首先給出舉例python項(xiàng)目的spec文件配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | # -*- mode: python -*- import sys import os.path as osp sys.setrecursionlimit( 5000 ) block_cipher = None SETUP_DIR = 'D:\\install_test\\FASTPLOT\\' a = Analysis([ 'fastplot.py' , 'frozen_dir.py' , 'D:\\install_test\\FASTPLOT\\lib\\app\\start.py' , 'D:\\install_test\\FASTPLOT\\lib\\models\\analysis_model.py' , 'D:\\install_test\\FASTPLOT\\lib\\models\\datafile_model.py' , 'D:\\install_test\\FASTPLOT\\lib\\models\\data_model.py' , 'D:\\install_test\\FASTPLOT\\lib\\models\\figure_model.py' , 'D:\\install_test\\FASTPLOT\\lib\\models\\time_model.py' , 'D:\\install_test\\FASTPLOT\\lib\\models\\mathematics_model.py' , 'D:\\install_test\\FASTPLOT\\lib\\views\\constant.py' , 'D:\\install_test\\FASTPLOT\\lib\\views\\custom_dialog.py' , 'D:\\install_test\\FASTPLOT\\lib\\views\\data_dict_window.py' , 'D:\\install_test\\FASTPLOT\\lib\\views\\data_process_window.py' , 'D:\\install_test\\FASTPLOT\\lib\\views\\data_sift_window.py' , 'D:\\install_test\\FASTPLOT\\lib\\views\\mathematics_window.py' , 'D:\\install_test\\FASTPLOT\\lib\\views\\para_temp_window.py' , 'D:\\install_test\\FASTPLOT\\lib\\views\\mainwindow.py' , 'D:\\install_test\\FASTPLOT\\lib\\views\\paralist_window.py' , 'D:\\install_test\\FASTPLOT\\lib\\views\\plot_window.py' ], pathex = [ 'D:\\install_test\\FASTPLOT' ], binaries = [], datas = [(SETUP_DIR + 'lib\\icon' , 'lib\\icon' ),(SETUP_DIR + 'data' , 'data' )], hiddenimports = [ 'pandas' , 'pandas._libs' , 'pandas._libs.tslibs.np_datetime' , 'pandas._libs.tslibs.timedeltas' , 'pandas._libs.tslibs.nattype' , 'pandas._libs.skiplist' , 'scipy._lib' , 'scipy._lib.messagestream' ], hookspath = [], runtime_hooks = [], excludes = [], win_no_prefer_redirects = False , win_private_assemblies = False , cipher = block_cipher) pyz = PYZ(a.pure, a.zipped_data, cipher = block_cipher) exe = EXE(pyz, a.scripts, exclude_binaries = True , name = 'fastplot' , debug = False , strip = False , upx = True , console = True ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip = False , upx = True , name = 'fastplot' ) |
a) py文件打包配置
針對(duì)多目錄多文件的python項(xiàng)目,打包時(shí)候需要將所有相關(guān)的py文件輸入到Analysis類里。Analysis類中的pathex定義了打包的主目錄,對(duì)于在此目錄下的py文件可以只寫文件名不寫路徑。如上的spec腳本,將所有項(xiàng)目中的py文件路徑以列表形式寫入Analysis,這里為了說明混合使用了絕對(duì)路徑和相對(duì)路徑。
b) 資源文件打包配置
資源文件包括打包的python項(xiàng)目使用的相關(guān)文件,如圖標(biāo)文件,文本文件等。對(duì)于此類資源文件的打包需要設(shè)置Analysis的datas,如例子所示datas接收元組:datas=[(SETUP_DIR+'lib\\icon','lib\\icon'),(SETUP_DIR+'data','data')]。元組的組成為(原項(xiàng)目中資源文件路徑,打包后路徑),例子中的(SETUP_DIR+'lib\\icon','lib\\icon')表示從D:\\install_test\\FASTPLOT\\lib\\icon下的圖標(biāo)文件打包后放入打包結(jié)果路徑下的lib\\icon目錄。
c)Hidden import配置
pyinstaller在進(jìn)行打包時(shí),會(huì)解析打包的python文件,自動(dòng)尋找py源文件的依賴模塊。但是pyinstaller解析模塊時(shí)可能會(huì)遺漏某些模塊(not visible to the analysis phase),造成打包后執(zhí)行程序時(shí)出現(xiàn)類似No Module named xxx。這時(shí)我們就需要在Analysis下hiddenimports中加入遺漏的模塊,如例子中所示。
d)遞歸深度設(shè)置
在打包導(dǎo)入某些模塊時(shí),常會(huì)出現(xiàn)"RecursionError: maximum recursion depth exceeded"的錯(cuò)誤,這可能是打包時(shí)出現(xiàn)了大量的遞歸超出了python預(yù)設(shè)的遞歸深度。因此需要在spec文件上添加遞歸深度的設(shè)置,設(shè)置一個(gè)足夠大的值來保證打包的進(jìn)行,即
1 2 | import sys sys.setrecursionlimit( 5000 ) |
3.使用spec執(zhí)行打包命令
pyinstaller -D xxx.spec
打包生成兩個(gè)文件目錄build和dist,build為臨時(shí)文件目錄完成打包后可以刪除;dist中存放打包的結(jié)果,可執(zhí)行文件和其它程序運(yùn)行的關(guān)聯(lián)文件都在這個(gè)目錄下。
Python模塊的打包問題
程序調(diào)用的很多包,在打包時(shí)候可能會(huì)出現(xiàn)一些問題,針對(duì)這寫問題需要做一些處理才能保證打包的程序正常執(zhí)行。
1.PyQt plugins缺失
使用PyQt編寫UI交互界面的python代碼在進(jìn)行打包時(shí)可能會(huì)出現(xiàn)一些特別的問題。
執(zhí)行使用了PyQt的打包程序,常會(huì)出現(xiàn)這樣的錯(cuò)誤,提示缺少Q(mào)t platfrom plugin “windows”,如下圖
打包后程序運(yùn)行后,使用png格式的圖標(biāo)可以正常顯示,但使用的ico格式圖標(biāo)不顯示(對(duì)于所有圖標(biāo)和關(guān)聯(lián)文件都無法使用的情況涉及到路徑問題,后文會(huì)另外解釋)。
這兩個(gè)錯(cuò)誤產(chǎn)生的問題都是因?yàn)榇虬鼤r(shí)沒有將PyQt相關(guān)的動(dòng)態(tài)鏈接庫(kù)目錄生成到打包目錄下,因此可以通過將這些需要的文件目錄拷貝到打包生成目錄下,解決plugin缺失問題。以使用PyQt5編寫的python軟件打包為例,完成打包后的結(jié)果目錄下包含PyQt5文件夾,將PyQt5\Qt\plugins下的所有內(nèi)容(如下圖)拷貝到打包結(jié)果目錄。這樣就可以解決PyQt plugins缺失的問題。
2.動(dòng)態(tài)鏈接庫(kù)缺失問題
更一般的,打包后可能會(huì)缺失某些動(dòng)態(tài)鏈接庫(kù),造成執(zhí)行程序出錯(cuò),如
ImportError: DLL load failed: 找不到指定的模塊
在打包過程中一般會(huì)有與此相關(guān)的warning提示(lib not found)無法找到這些動(dòng)態(tài)鏈接庫(kù)。例如在32位版本的打包中,可能會(huì)出現(xiàn)scipy模塊相關(guān)的dll文件無法找到。這時(shí)就需要在打包的spec文件中指定動(dòng)態(tài)鏈接庫(kù)路徑,使其關(guān)聯(lián)到打包后的路徑中。
binaries=[('C:\\Program Files\\Python36-32\\Lib\\site-packages\\scipy\\extra-dll','.')]
Analysis下的binaries是為打包文件添加二進(jìn)制文件,缺失的動(dòng)態(tài)鏈接庫(kù)可以通過這種方式自動(dòng)加入到打包路徑中。
3.窗體風(fēng)格變化問題
在某些情況下,如在精簡(jiǎn)環(huán)境下的python程序打包中,執(zhí)行打包后的程序會(huì)出現(xiàn)窗體風(fēng)格變?yōu)槔鲜降膚in風(fēng)格,這是由于打包時(shí)候PyQt的styles動(dòng)態(tài)庫(kù)沒有找到。因此只需要在Python 目錄下找到 Lib\site-packages\PyQt5\Qt\plugins\styles,將styles整個(gè)目錄復(fù)制到打包結(jié)果目錄。
凍結(jié)打包路徑
執(zhí)行打包后的程序,經(jīng)常會(huì)出現(xiàn)程序使用的圖標(biāo)無法顯示,程序使用的關(guān)聯(lián)文件無法關(guān)聯(lián)?;蛘撸诖虬谋緳C(jī)上運(yùn)行正常,但是將打包后的程序放到其它機(jī)器上就有問題。這些現(xiàn)象都很有可能是由程序使用的文件路徑發(fā)生改變產(chǎn)生的,因此在打包時(shí)候我們需要根據(jù)執(zhí)行路徑進(jìn)行路徑“凍結(jié)”。
1.使用絕對(duì)路徑
在python代碼中使用絕對(duì)路徑調(diào)用外部文件可以保證打包時(shí)候路徑可追溯,因此在本機(jī)上運(yùn)行打包后程序基本沒問題。但是當(dāng)本機(jī)上對(duì)應(yīng)路徑的資源文件被改變,或者將打包程序應(yīng)用到別的機(jī)器,都會(huì)出現(xiàn)搜索不到資源文件的問題。這種方式不是合適的打包發(fā)布python軟件的方式。
2.使用凍結(jié)路徑
增加一個(gè)py文件,例如叫frozen_dir.py
# -*- coding: utf-8 -*-
"""
Created on Sat Aug 25 22:41:09 2018
frozen dir
@author: yanhua
"""
import sys
import os
def app_path():
"""Returns the base application path."""
if hasattr(sys, 'frozen'):
# Handles PyInstaller
return os.path.dirname(sys.executable)
return os.path.dirname(__file__)
其中的app_path()函數(shù)返回一個(gè)程序的執(zhí)行路徑,為了方便我們將此文件放在項(xiàng)目文件的根目錄,通過這種方式建立了相對(duì)路徑的關(guān)系。
源代碼中使用路徑時(shí),以app_path()的返回值作為基準(zhǔn)路徑,其它路徑都是其相對(duì)路徑。以本文中使用的python項(xiàng)目打包為例,如下所示
import frozen_dir
SETUP_DIR = frozen_dir.app_path()
FONT_MSYH = matplotlib.font_manager.FontProperties(
fname = SETUP_DIR + r'\data\fonts\msyh.ttf',
size = 8)
DIR_HELP_DOC = SETUP_DIR + r'\data\docs'
DIR_HELP_VIDEO = SETUP_DIR + r'\data\videos'
通過凍結(jié)路徑,使用了基準(zhǔn)目錄下的data目錄下的fonts, docs, videos。
主程序中也做了類似的調(diào)整,改變其設(shè)置路徑方法
import frozen_dir
SETUP_DIR = frozen_dir.app_path()+r'\lib'
sys.path.append(SETUP_DIR)
使用這樣的方法進(jìn)行打包,打包后的可執(zhí)行程序就可以在其它機(jī)器上運(yùn)行。
其它問題
由于操作系統(tǒng)和運(yùn)行環(huán)境的不同,pyinstaller打包中還可能遇到很多其它問題,最后總結(jié)一些我在打包中遇到的其它坑:
1.權(quán)限問題
通常時(shí)在打包時(shí)出現(xiàn)的某些文件拒絕訪問或沒有權(quán)限執(zhí)行某些操作等。解決這個(gè)的方法一般有這幾個(gè)方面:
a)使用管理員權(quán)限運(yùn)行cmd或其它命令行窗口
b)關(guān)閉殺毒軟件
c)使用完全權(quán)限的管理員賬戶
2.中文路徑
pyinstaller打包后的路徑使用中文沒有問題,不過為了減少打包時(shí)候出錯(cuò)的可能,盡量將打包使用的資源文件和代碼文件路徑設(shè)置為英文。
3.打包后文件的大小
通常python打包為可執(zhí)行文件都會(huì)得到一個(gè)較大的包,這是無法避免的,但是我們還是可以通過一些方法來盡量精簡(jiǎn)打包后的執(zhí)行程序:
a)在代碼中減少不必要的import,如from xxx import *
b)在精簡(jiǎn)的運(yùn)行環(huán)境(如原生python環(huán)境)下打包,缺什么包就下什么包,避免不必要的python包被打包入程序。尤其是anaconda這樣的集成環(huán)境下打包的結(jié)果會(huì)大很多。
c)使用UPX
---------------------
轉(zhuǎn)載自
作者:YanHua_jake
來源:CSDN
原文:https://blog.csdn.net/weixin_42052836/article/details/82315118
聯(lián)系客服