使用pyinstaller打包多文件和目錄的Python項目

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

 

目錄python

  Pyinstaller基本使用方法windows

  Python項目的打包方法app

    1.spec文件生成ide

    2.spec文件配置函數

    3.使用spec執行打包命令ui

  Python模塊的打包問題spa

  凍結打包路徑操作系統

  其它問題.net

 

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)

  

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

 

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整個目錄複製到打包結果目錄。

  凍結打包路徑
  執行打包後的程序,常常會出現程序使用的圖標沒法顯示,程序使用的關聯文件沒法關聯。或者,在打包的本機上運行正常,可是將打包後的程序放到其它機器上就有問題。這些現象都頗有多是由程序使用的文件路徑發生改變產生的,所以在打包時候咱們須要根據執行路徑進行路徑「凍結」。

  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
---------------------

轉載自做者:YanHua_jake 來源:CSDN 原文:https://blog.csdn.net/weixin_42052836/article/details/82315118

相關文章
相關標籤/搜索