Python3中采用PyInstaller打包工程項目


對自己完成的工程項目進行打包,因為是第一次嘗試,踩了各種各樣的坑。以防下次繼續踩坑,把相關問題以及解決辦法記錄下來。此次打包采用Python3.6,PyInstaller3.6,Windows64位系統。接下來就是整篇文章的精華了。

一、PyInstaller安裝

  PyInstaller包的安裝可以在Anaconda環境下以conda install pyinstaller進行安裝,在PyCharm中可以通過pip install pyinstaller進行安裝。安裝成功后就可以着手進行打包了。當然打包需要用到以下一些相關命令了。

-h,--help 查看該模塊的幫助信息
-F,-onefile 產生單個的可執行文件
-D,--onedir 產生一個目錄(包含多個文件)作為可執行程序
-a,--ascii 不包含 Unicode 字符集支持
-d,--debug 產生 debug 版本的可執行文件
-w,--windowed,--noconsolc 指定程序運行時不顯示命令行窗口(僅對 Windows 有效)
-c,--nowindowed,--console 指定使用命令行窗口運行程序(僅對 Windows 有效)
-o DIR,--out=DIR 指定 spec 文件的生成目錄。如果沒有指定,則默認使用當前目錄來生成 spec 文件
-p DIR,--path=DIR 設置 Python 導入模塊的路徑(和設置 PYTHONPATH 環境變量的作用相似)。也可使用路徑分隔符(Windows 使用分號,Linux 使用冒號)來分隔多個路徑
-n NAME,--name=NAME 指定項目(產生的 spec)名字。如果省略該選項,那么第一個腳本的主文件名將作為 spec 的名字

  常用到的命令為-F、-D、-i、-p、-w等,其中-i用於指定生成項目的圖標,需要使用絕對路徑。對於打包結果較大的項目,選用-d生成目錄相比單可執行文件的打包方式,執行速度更快,但包含更加多的文件。本文的例子選中-D方式打包。

二、Python項目打包

  打包分為單文件打包以及工程文件打包,單文件打包直接在命令窗口中采用pyinstaller -D filename.py,具體命令的添加可以參考表格中的命令。工程文件打包稍微有一些麻煩,主要是先生成主窗口的.spec文件,並修改相應的內容,最終執行.spec文件就可以逐步實現打包。以下是本次打包的工程目錄,主要文件均在moleculeSystem,還包括img文件夾,tempFile文件夾等眾多文件。

  

 

 

1.SPEC文件的生成

  通過使用命令pyi-makespec -w xxx.py能夠生成相應的xxx.spec文件,具體如下:

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(['mainWin.py'],
             pathex=['D:\\Python\\untitled1\\moleculeSystem'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             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='mainWin',
          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='mainWin')

  spec文件中主要包含4個class: Analysis, PYZ, EXE和COLLECT.

  • Analysis以py文件為輸入,它會分析py文件的依賴模塊,並生成相應的信息
  • PYZ是一個.pyz的壓縮包,包含程序運行需要的所有依賴
  • EXE根據上面兩項生成
  • COLLECT生成其他部分的輸出文件夾,COLLECT也可以沒有

首先給出舉例項目的spec文件:

# -*- mode: python ; coding: utf-8 -*-

block_cipher = None
SETUP_DIR = 'D:\\Python\\untitled1\\'

a = Analysis(['mainWin.py'],
             pathex=['D:\\Python\\untitled1\\moleculeSystem'],
             binaries=[],
             datas=[(SETUP_DIR+'img','img'),(SETUP_DIR+'temp_img','temp_img'),(SETUP_DIR+'mol\\totalFile','mol\\totalFile'),(SETUP_DIR+'tempFile','tempFile'),('D:\\Anaconda\\Anaconda3\\Lib\\site-packages\\vtkmodules','vtkmodules'),(SETUP_DIR+'AtomsInfo.txt','AtomsInfo.txt'),(SETUP_DIR+'BondInfos.txt','BondInfos.txt')],
             hiddenimports=['pkg_resources'],
             hookspath=[],
             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='mainWin',
          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='mainWin')

  

1)py文件打包配置

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

2) 資源文件打包配置

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

3)Hidden import配置

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

4)遞歸深度設置

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

import sys
sys.setrecursionlimit(5000)

5)去除不必要的模塊import

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

pyinstaller -D xxx.spec

打包生成兩個文件目錄build和dist,build為臨時文件目錄完成打包后可以刪除;dist中存放打包的結果,可執行文件和其它程序運行的關聯文件都在這個目錄下。

 

三、打包出現的問題

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=[('路徑','.')]

3.Failed to execute script pyi_rth_pkgres

 

打包運行xxx.exe文件出現上述問題,是有可能因PyInstaller版本不是最新的造成的,可以通過GitHub網站https://github.com/pyinstaller/pyinstaller/archive/develop.zip下載,下載后解壓到某一文件夾中,采用命令行的方式進入解壓后的文件夾中,使用命令python setup.py install進行安裝。

4.Failed to execute script pyi_rth_certifi

出現此錯誤的原因是缺少了ssl證書,因此可以從Python官網上下載相應python版本的壓縮包,解壓后將解壓包中的_ssl.pyd(復制到Anaconda目錄下DLLs下覆蓋原文件),將

libcrypto-1_1.dll、libssl-1_1.dll(復制到Anaconda根目錄)即可解決問題。

5.no modules named xxxx

出現該問題是打包后的根目錄中沒有所需要的模塊,因此可以將對應安裝庫中的文件復制到根目錄中,此錯誤就不再出現。或者可以在hiddenimports中加入缺失的包。或者在datas中作同樣的處理。

6.Failed to execute script xxxx

出現該問題是在引用同級目錄中的文件時出現,在有python文件出增加__init__.py文件即可解決,該文件可什么都不寫。

7.路徑凍結

增加一個py文件,例如叫frozenPath.py

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 frozenPath
# 根目錄路徑
appPath = frozenPath.app_path()
background = QtGui.QPixmap(appPath+"/img/1.png")

這樣不論打包的文件放在哪台電腦上運行都不會出現路徑錯誤。

8.始終還有問題存在

可以采用以下命令解決

pyinstaller --hidden-import=pkg_resources -F xxxx.py

9.順便再記個PyQt5中QSplitter部件無法顯示其他窗口的背景

原因在於主窗口部件無法顯示背景,子窗口部件能夠顯示,基於此可以解決這種問題。

        # 設置背景圖片
        background = QtGui.QPixmap(appPath+"/img/1.png")
        palette1 = QtGui.QPalette()
        palette1.setBrush(self.backgroundRole(),QtGui.QBrush(background))
        self.handle.setPalette(palette1)
        self.handle.setAutoFillBackground(True)
        # 設置背景透明
        self.main_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)

10.安裝PyQt5

使用豆瓣能夠告訴下載

下載PyQt5
pip install PyQt5 -i https://pypi.douban.com/simple
下載PyQt5-tools
pip install PyQt5-tools -i https://pypi.douban.com/simple

 

希望這些能夠對還在打包程序路上掙扎的同志們有所幫助。

 

 

 

 

 

 

  

 


免責聲明!

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



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