Pyinstaller 打包發(fā)布經(jīng)驗(yàn)總結(jié)
使用Pyinstaller打包Python項(xiàng)目包含了大量的坑,這篇文章總結(jié)實(shí)踐得到的Pyinstaller打包經(jīng)驗(yàn)。本文的例子為Python3.6代碼,Pyinstaller3.4,在windows下打包為64位和32位版本。
Pyinstaller基本使用方法
Pyinstaller可以通過(guò)簡(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, --onedir | Create a one-folder bundle containing an executable (default) |
-F, --onefile | Create 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)行自定義配置的打包,首先需要編寫(xiě)打包的配置文件.spec文件。當(dāng)使用pyinstaller -d xxx.py時(shí)候會(huì)生成默認(rèn)的xxx.spec文件進(jìn)行默認(rèn)的打包配置。通過(guò)配置spec腳本,并執(zhí)行pyinstaller -d xxx.spec完成自定義的打包。
通過(guò)生成spec文件的命令,針對(duì)代碼的主程序文件生成打包對(duì)應(yīng)的spec文件
pyi-makespec -w xxx.py
打開(kāi)生成的spec文件,修改其默認(rèn)腳本,完成自定義打包需要的配置。spec文件是一個(gè)python腳本,其默認(rèn)的結(jié)構(gòu)如下例所示
# -*- 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也可以沒(méi)有
2.spec文件配置
首先給出舉例python項(xiàng)目的spec文件配置
# -*- 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文件可以只寫(xiě)文件名不寫(xiě)路徑。如上的spec腳本,將所有項(xiàng)目中的py文件路徑以列表形式寫(xiě)入Analysis,這里為了說(shuō)明混合使用了絕對(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è)足夠大的值來(lái)保證打包的進(jìn)行,即
import sys sys.setrecursionlimit(5000)
e)去除不必要的模塊import
有時(shí)需要讓pyinstaller不打包某些用不到的模塊,可通過(guò)在excludes=[]中添加此模塊實(shí)現(xiàn),如
excludes=['zmq']
3.使用spec執(zhí)行打包命令
pyinstaller -D xxx.spec
打包生成兩個(gè)文件目錄build和dist,build為臨時(shí)文件目錄完成打包后可以刪除;dist中存放打包的結(jié)果,可執(zhí)行文件和其它程序運(yùn)行的關(guān)聯(lián)文件都在這個(gè)目錄下。
Visual C++ run-time .dlls包含
針對(duì)在Windows<10發(fā)布使用,且Python>=3.5的情況,Pyinstaller打包的程序可能會(huì)出現(xiàn)不包含Visual C++ run-time .dlls的情況,Python>=3.5需要使用Visual Studio 2015 run-time,也就是Universal CRT,這些runtime在Win10本身或Win7到Win8.1版本的更新包里,但程序打包后使用的系統(tǒng)里并不一定安裝了。因此需要參考Universal CRT的建議,應(yīng)用以下的方法解決這個(gè)問(wèn)題:
Build on Windows 7 which has been reported to work.
Include one of the VCRedist packages (the redistributable package files) into your application's installer. This is Microsoft's recommended way, see “Distributing Software that uses the Universal CRT“ in the above-mentioned link, numbers 2 and 3.
Install the Windows Software Development Kit (SDK) for Windows 10 and expand the .spec-file to include the required DLLs, see “Distributing Software that uses the Universal CRT“ in the above-mentioned link, number 6.
Python模塊的打包問(wèn)題
程序調(diào)用的很多包,在打包時(shí)候可能會(huì)出現(xiàn)一些問(wèn)題,針對(duì)這寫(xiě)問(wèn)題需要做一些處理才能保證打包的程序正常執(zhí)行。
1.PyQt plugins缺失
使用PyQt編寫(xiě)UI交互界面的python代碼在進(jìn)行打包時(shí)可能會(huì)出現(xiàn)一些特別的問(wè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)文件都無(wú)法使用的情況涉及到路徑問(wèn)題,后文會(huì)另外解釋)。
這兩個(gè)錯(cuò)誤產(chǎn)生的問(wèn)題都是因?yàn)榇虬鼤r(shí)沒(méi)有將PyQt相關(guān)的動(dòng)態(tài)鏈接庫(kù)目錄生成到打包目錄下,因此可以通過(guò)將這些需要的文件目錄拷貝到打包生成目錄下,解決plugin缺失問(wèn)題。以使用PyQt5編寫(xiě)的python軟件打包為例,完成打包后的結(jié)果目錄下包含PyQt5文件夾,將PyQt5\Qt\plugins下的所有內(nèi)容(如下圖)拷貝到打包結(jié)果目錄。這樣就可以解決PyQt plugins缺失的問(wèn)題。
2.動(dòng)態(tài)鏈接庫(kù)缺失問(wèn)題
更一般的,打包后可能會(huì)缺失某些動(dòng)態(tài)鏈接庫(kù),造成執(zhí)行程序出錯(cuò),如
ImportError: DLL load failed: 找不到指定的模塊
在打包過(guò)程中一般會(huì)有與此相關(guān)的warning提示(lib not found)無(wú)法找到這些動(dòng)態(tài)鏈接庫(kù)。例如在32位版本的打包中,可能會(huì)出現(xiàn)scipy模塊相關(guān)的dll文件無(wú)法找到。這時(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ù)可以通過(guò)這種方式自動(dòng)加入到打包路徑中。
3.窗體風(fēng)格變化問(wèn)題
在某些情況下,如在精簡(jiǎn)環(huán)境下的python程序打包中,執(zhí)行打包后的程序會(huì)出現(xiàn)窗體風(fēng)格變?yōu)槔鲜降膚in風(fēng)格,這是由于打包時(shí)候PyQt的styles動(dòng)態(tài)庫(kù)沒(méi)有找到。因此只需要在Python 目錄下找到 Lib\site-packages\PyQt5\Qt\plugins\styles,將styles整個(gè)目錄復(fù)制到打包結(jié)果目錄。
4.UnicodeDecodeError
當(dāng)打包時(shí)出現(xiàn)類似錯(cuò)誤時(shí):
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xce in position 122
可在打包的命令行中輸入chcp 65001設(shè)置命令行顯示utf-8字符,然后再執(zhí)行打包命令?;蛘撸薷膒yinstaller包下的compat.py,根據(jù)報(bào)錯(cuò)對(duì)應(yīng)的行將
out = out.decode(encoding)
改為
out = out.decode(encoding, 'replace')
凍結(jié)打包路徑
執(zhí)行打包后的程序,經(jīng)常會(huì)出現(xiàn)程序使用的圖標(biāo)無(wú)法顯示,程序使用的關(guān)聯(lián)文件無(wú)法關(guān)聯(lián)?;蛘撸诖虬谋緳C(jī)上運(yùn)行正常,但是將打包后的程序放到其它機(jī)器上就有問(wèn)題。這些現(xiàn)象都很有可能是由程序使用的文件路徑發(fā)生改變產(chǎn)生的,因此在打包時(shí)候我們需要根據(jù)執(zhí)行路徑進(jìn)行路徑“凍結(jié)”。
1.使用絕對(duì)路徑
在python代碼中使用絕對(duì)路徑調(diào)用外部文件可以保證打包時(shí)候路徑可追溯,因此在本機(jī)上運(yùn)行打包后程序基本沒(méi)問(wèn)題。但是當(dāng)本機(jī)上對(duì)應(yīng)路徑的資源文件被改變,或者將打包程序應(yīng)用到別的機(jī)器,都會(huì)出現(xiàn)搜索不到資源文件的問(wè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)目文件的根目錄,通過(guò)這種方式建立了相對(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'
通過(guò)凍結(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)行。
其它問(wèn)題
由于操作系統(tǒng)和運(yùn)行環(huán)境的不同,pyinstaller打包中還可能遇到很多其它問(wèn)題,最后總結(jié)一些我在打包中遇到的其它坑:
1.權(quán)限問(wèn)題
通常時(shí)在打包時(shí)出現(xiàn)的某些文件拒絕訪問(wèn)或沒(méi)有權(quán)限執(zhí)行某些操作等。解決這個(gè)的方法一般有這幾個(gè)方面:
a)使用管理員權(quán)限運(yùn)行cmd或其它命令行窗口
b)關(guān)閉殺毒軟件
c)使用完全權(quán)限的管理員賬戶
2.中文路徑
pyinstaller打包后的路徑使用中文沒(méi)有問(wèn)題,不過(guò)為了減少打包時(shí)候出錯(cuò)的可能,盡量將打包使用的資源文件和代碼文件路徑設(shè)置為英文。
3.打包后文件的大小
通常python打包為可執(zhí)行文件都會(huì)得到一個(gè)較大的包,這是無(wú)法避免的,但是我們還是可以通過(guò)一些方法來(lái)盡量精簡(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
到此這篇關(guān)于Pyinstaller 打包發(fā)布經(jīng)驗(yàn)總結(jié)的文章就介紹到這了,更多相關(guān)Pyinstaller 打包發(fā)布內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
PyQt5實(shí)現(xiàn)讓QScrollArea支持鼠標(biāo)拖動(dòng)的操作方法
今天小編就為大家分享一篇PyQt5實(shí)現(xiàn)讓QScrollArea支持鼠標(biāo)拖動(dòng)的操作方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-06-06python+selenium 定位到元素,無(wú)法點(diǎn)擊的解決方法
今天小編就為大家分享一篇python+selenium 定位到元素,無(wú)法點(diǎn)擊的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01python實(shí)現(xiàn)簡(jiǎn)單的TCP代理服務(wù)器
這篇文章主要介紹了python實(shí)現(xiàn)簡(jiǎn)單的TCP代理服務(wù)器,包含了完整的實(shí)現(xiàn)過(guò)程及對(duì)應(yīng)的源碼與說(shuō)明文檔下載,非常具有參考借鑒價(jià)值,需要的朋友可以參考下2014-10-10利用插件和python實(shí)現(xiàn)Excel轉(zhuǎn)json的兩種辦法
轉(zhuǎn)換Excel表格到JSON格式有很多方法,下面這篇文章主要給大家介紹了關(guān)于利用插件和python實(shí)現(xiàn)Excel轉(zhuǎn)json的兩種辦法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11Django 1.10以上版本 url 配置注意事項(xiàng)詳解
這篇文章主要介紹了Django 1.10以上版本 url 配置注意事項(xiàng)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08Python中paramiko模塊的基礎(chǔ)操作與排錯(cuò)問(wèn)題
python的ssh庫(kù)操作需要引入一個(gè)遠(yuǎn)程控制的模塊——paramiko,可用于對(duì)遠(yuǎn)程服務(wù)器進(jìn)行命令或文件操作,這篇文章主要介紹了Python學(xué)習(xí)之paramiko模塊的基礎(chǔ)操作與排錯(cuò),需要的朋友可以參考下2022-09-09