Pyinstaller 打包發布經驗總結


 

轉載於 YanHua_jake  於 2018-09-02 18:46:30 發布:https://blog.csdn.net/weixin_42052836/article/details/82315118


使用Pyinstaller打包Python項目包含了大量的坑,這篇文章總結實踐得到的Pyinstaller打包經驗。本文的例子為Python3.6代碼,Pyinstaller3.4,在windows下打包為64位和32位版本。

目錄

Pyinstaller基本使用方法

Python項目的打包方法

1.spec文件生成

2.spec文件配置

3.使用spec執行打包命令

Visual C++ run-time .dlls包含

Python模塊的打包問題

凍結打包路徑

其它問題

Pyinstaller基本使用方法

Python項目的打包方法

1.spec文件生成

2.spec文件配置

3.使用spec執行打包命令

Python模塊的打包問題

凍結打包路徑

其它問題

Pyinstaller基本使用方法
Pyinstaller可以通過簡單的命令進行python代碼的打包工作,其基本的命令為:

pyinstaller -option xxx.py
options的詳情可參考官方幫助文檔https://pyinstaller.readthedocs.io/en/stable/usage.html

這邊只介紹用到的option:-d生成一個文件目錄包含可執行文件和相關動態鏈接庫和資源文件等;-f僅生成一個可執行文件

-D, --onedir Create a one-folder bundle containing an executable (default)
-F, --onefile Create a one-file bundled executable.
對於打包結果較大的項目,選用-d生成目錄相比單可執行文件的打包方式,執行速度更快,但包含更加多的文件。本文的例子選中-D方式打包。

Python項目的打包方法
以一個多文件和目錄的Python項目為例,項目文件包含:1.Python源代碼文件;2.圖標資源文件;3.其它資源文件

以圖中項目為例,Python源代碼文件在多個目錄下:bin, lib\app, lib\models, lib\views;圖標資源文件在lib\icon目錄下;其它資源文件在data目錄下,包括文本文件,視頻文件等等。

 

1.spec文件生成
為了進行自定義配置的打包,首先需要編寫打包的配置文件.spec文件。當使用pyinstaller -d xxx.py時候會生成默認的xxx.spec文件進行默認的打包配置。通過配置spec腳本,並執行pyinstaller -d xxx.spec完成自定義的打包。

通過生成spec文件的命令,針對代碼的主程序文件生成打包對應的spec文件

pyi-makespec -w xxx.py
打開生成的spec文件,修改其默認腳本,完成自定義打包需要的配置。spec文件是一個python腳本,其默認的結構如下例所示

# -*- 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個class: Analysis, PYZ, EXE和COLLECT.

Analysis以py文件為輸入,它會分析py文件的依賴模塊,並生成相應的信息

PYZ是一個.pyz的壓縮包,包含程序運行需要的所有依賴

EXE根據上面兩項生成

COLLECT生成其他部分的輸出文件夾,COLLECT也可以沒有

2.spec文件配置
首先給出舉例python項目的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文件打包配置

針對多目錄多文件的python項目,打包時候需要將所有相關的py文件輸入到Analysis類里。Analysis類中的pathex定義了打包的主目錄,對於在此目錄下的py文件可以只寫文件名不寫路徑。如上的spec腳本,將所有項目中的py文件路徑以列表形式寫入Analysis,這里為了說明混合使用了絕對路徑和相對路徑。

b) 資源文件打包配置

資源文件包括打包的python項目使用的相關文件,如圖標文件,文本文件等。對於此類資源文件的打包需要設置Analysis的datas,如例子所示datas接收元組:datas=[(SETUP_DIR+'lib\\icon','lib\\icon'),(SETUP_DIR+'data','data')]。元組的組成為(原項目中資源文件路徑,打包后路徑),例子中的(SETUP_DIR+'lib\\icon','lib\\icon')表示從D:\\install_test\\FASTPLOT\\lib\\icon下的圖標文件打包后放入打包結果路徑下的lib\\icon目錄。

c)Hidden import配置

pyinstaller在進行打包時,會解析打包的python文件,自動尋找py源文件的依賴模塊。但是pyinstaller解析模塊時可能會遺漏某些模塊(not visible to the analysis phase),造成打包后執行程序時出現類似No Module named xxx。這時我們就需要在Analysis下hiddenimports中加入遺漏的模塊,如例子中所示。

d)遞歸深度設置

在打包導入某些模塊時,常會出現"RecursionError: maximum recursion depth exceeded"的錯誤,這可能是打包時出現了大量的遞歸超出了python預設的遞歸深度。因此需要在spec文件上添加遞歸深度的設置,設置一個足夠大的值來保證打包的進行,即

import sys
sys.setrecursionlimit(5000)
e)去除不必要的模塊import

有時需要讓pyinstaller不打包某些用不到的模塊,可通過在excludes=[]中添加此模塊實現,如

excludes=['zmq']
3.使用spec執行打包命令
pyinstaller -D xxx.spec
打包生成兩個文件目錄build和dist,build為臨時文件目錄完成打包后可以刪除;dist中存放打包的結果,可執行文件和其它程序運行的關聯文件都在這個目錄下。

 

Visual C++ run-time .dlls包含
針對在Windows<10發布使用,且Python>=3.5的情況,Pyinstaller打包的程序可能會出現不包含Visual C++ run-time .dlls的情況,Python>=3.5需要使用Visual Studio 2015 run-time,也就是Universal CRT,這些runtime在Win10本身或Win7到Win8.1版本的更新包里,但程序打包后使用的系統里並不一定安裝了。因此需要參考Universal CRT的建議,應用以下的方法解決這個問題:

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模塊的打包問題
程序調用的很多包,在打包時候可能會出現一些問題,針對這寫問題需要做一些處理才能保證打包的程序正常執行。

1.PyQt plugins缺失

使用PyQt編寫UI交互界面的python代碼在進行打包時可能會出現一些特別的問題。

執行使用了PyQt的打包程序,常會出現這樣的錯誤,提示缺少Qt platfrom plugin “windows”,如下圖

 

打包后程序運行后,使用png格式的圖標可以正常顯示,但使用的ico格式圖標不顯示(對於所有圖標和關聯文件都無法使用的情況涉及到路徑問題,后文會另外解釋)。

這兩個錯誤產生的問題都是因為打包時沒有將PyQt相關的動態鏈接庫目錄生成到打包目錄下,因此可以通過將這些需要的文件目錄拷貝到打包生成目錄下,解決plugin缺失問題。以使用PyQt5編寫的python軟件打包為例,完成打包后的結果目錄下包含PyQt5文件夾,將PyQt5\Qt\plugins下的所有內容(如下圖)拷貝到打包結果目錄。這樣就可以解決PyQt plugins缺失的問題。

 

2.動態鏈接庫缺失問題

更一般的,打包后可能會缺失某些動態鏈接庫,造成執行程序出錯,如

ImportError: DLL load failed: 找不到指定的模塊
在打包過程中一般會有與此相關的warning提示(lib not found)無法找到這些動態鏈接庫。例如在32位版本的打包中,可能會出現scipy模塊相關的dll文件無法找到。這時就需要在打包的spec文件中指定動態鏈接庫路徑,使其關聯到打包后的路徑中。

binaries=[('C:\\Program Files\\Python36-32\\Lib\\site-packages\\scipy\\extra-dll','.')]
Analysis下的binaries是為打包文件添加二進制文件,缺失的動態鏈接庫可以通過這種方式自動加入到打包路徑中。

3.窗體風格變化問題

在某些情況下,如在精簡環境下的python程序打包中,執行打包后的程序會出現窗體風格變為老式的win風格,這是由於打包時候PyQt的styles動態庫沒有找到。因此只需要在Python 目錄下找到 Lib\site-packages\PyQt5\Qt\plugins\styles,將styles整個目錄復制到打包結果目錄。

4.UnicodeDecodeError

當打包時出現類似錯誤時:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xce in position 122
可在打包的命令行中輸入chcp 65001設置命令行顯示utf-8字符,然后再執行打包命令。或者,修改pyinstaller包下的compat.py,根據報錯對應的行將

out = out.decode(encoding)
改為

out = out.decode(encoding, 'replace')
凍結打包路徑
執行打包后的程序,經常會出現程序使用的圖標無法顯示,程序使用的關聯文件無法關聯。或者,在打包的本機上運行正常,但是將打包后的程序放到其它機器上就有問題。這些現象都很有可能是由程序使用的文件路徑發生改變產生的,因此在打包時候我們需要根據執行路徑進行路徑“凍結”。

1.使用絕對路徑

在python代碼中使用絕對路徑調用外部文件可以保證打包時候路徑可追溯,因此在本機上運行打包后程序基本沒問題。但是當本機上對應路徑的資源文件被改變,或者將打包程序應用到別的機器,都會出現搜索不到資源文件的問題。這種方式不是合適的打包發布python軟件的方式。

2.使用凍結路徑

增加一個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()函數返回一個程序的執行路徑,為了方便我們將此文件放在項目文件的根目錄,通過這種方式建立了相對路徑的關系。

源代碼中使用路徑時,以app_path()的返回值作為基准路徑,其它路徑都是其相對路徑。以本文中使用的python項目打包為例,如下所示

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'
通過凍結路徑,使用了基准目錄下的data目錄下的fonts, docs, videos。

主程序中也做了類似的調整,改變其設置路徑方法

import frozen_dir

SETUP_DIR = frozen_dir.app_path()+r'\lib'
sys.path.append(SETUP_DIR)
使用這樣的方法進行打包,打包后的可執行程序就可以在其它機器上運行。

其它問題
由於操作系統和運行環境的不同,pyinstaller打包中還可能遇到很多其它問題,最后總結一些我在打包中遇到的其它坑:

1.權限問題

通常時在打包時出現的某些文件拒絕訪問或沒有權限執行某些操作等。解決這個的方法一般有這幾個方面:

a)使用管理員權限運行cmd或其它命令行窗口

b)關閉殺毒軟件

c)使用完全權限的管理員賬戶

2.中文路徑

pyinstaller打包后的路徑使用中文沒有問題,不過為了減少打包時候出錯的可能,盡量將打包使用的資源文件和代碼文件路徑設置為英文。

3.打包后文件的大小

通常python打包為可執行文件都會得到一個較大的包,這是無法避免的,但是我們還是可以通過一些方法來盡量精簡打包后的執行程序:

a)在代碼中減少不必要的import,如from xxx import *

b)在精簡的運行環境(如原生python環境)下打包,缺什么包就下什么包,避免不必要的python包被打包入程序。尤其是anaconda這樣的集成環境下打包的結果會大很多。

c)使用UPX
————————————————
版權聲明:本文為CSDN博主「YanHua_jake」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_42052836/article/details/82315118


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM