Pyinstaller打包完整python項目 使用虛擬環境的python項目的打包


Pyinstaller打包方式一般分為

第一種:直接輸入指令 

第二種:利用spec文件進行打包

由於直接輸入指令實際就是根據指令生成spec文件,再根據spec文件的內容進行打包操作,所以重點說明spec文件的內容

一 Pyinstaller直接輸入指令 

1.安裝pyinstaller

pip install pyinstaller

2.切換到工作目錄

cd xxxxxxxxxxx

3.簡單打包命令

可以用命令直接打包單文件,遇到項目文件很多時也可以用命令打包,但命令太長了過於繁雜,可以參考鏈接

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

pyinstaller -option xxx.py

假如項目啟動入口為xxx.py,首先需要以管理員身份啟動cmd,然后cd到xxx.py所在的文件夾

當我們使用命令行直接打包,即 pyinstaller -D xxx.py 時 ,代碼內部發生以下兩件事:

1.生成默認的xxx.spec文件

2.直接根據默認的main.spec文件進行執行pyinstaller -D xxx.spec完成默認的打包

options常用參數(按需求選擇):

  • -D   與 -F 相反用法,生成一個文件目錄包含可執行文件和相關動態鏈接庫和資源文件等,對於打包結果較大的項目,選用-D生成目錄相比-F的打包方式,執行速度更快,但包含更加多的文件
  • -F   表示在 dist 文件夾下只生成單個可執行文件(內部包含所有依賴),不加默認會在 dist 生成一大堆依賴文件+可執行文件。
  • -w  表示去掉控制台窗口,如果你的程序是有界面的,可以不寫這個參數,
  • -c   表示去掉窗框,使用控制台,推薦使用,會打印各種信息和log到控制台,加上這個參數生成的spec中的console=True
  • -p    表示自己定義需要加載的類路徑,項目中包含多個自建模塊的時候需要加上 -p aaa.py -p bbb.py -p ccc.py
  • -i     表示可執行文件的圖標,后面跟圖標的路徑,可以自定義exe文件的圖標,我嘗試了好多次沒成功

  打包完畢后在 dist 文件夾下雙擊項目啟動文件就可以執行了

二 Python利用spec文件進行打包完整項目的打包方法

當項目越來越大,引用的資源會越來越多時,那么使用pyinstaller進行打包,如果不利用spec文件,是很難滿足打包需求的。

其實你在使用 pyinstaller -option main.py簡單命令打包時 ,本質也是根據命令行中一系列的option參數,生成main.spec,然后自動執行pyinstaller main.spec完成打包

不過,如果你想把自己的資源文件一起打進包去,則需要對生成的spec文件進行一些手動編輯后,再手動執行 pyinstaller main.spec完成打包

參考:https://blog.csdn.net/weixin_42052836/article/details/82315118

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

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

 

1.生成 spec文件

為了實現自定義配置的打包:

第一步:需要生成啟動文件默認的spec文件:(選擇一個即可)

 pyi-makespec -w xxx.py 
 pyi-makespec -c xxx.py  # 生成的spec文件中console=True,即需要打印到控制台

第二步:打開生成的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文件

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

  • Analysis以py文件為輸入,它會分析py文件的依賴模塊,並生成相應的信息,修改的主要是這個部分

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

  • EXE根據上面兩項生成,里面包含圖標、版本(如果命令有會自動生成,命令沒傳也可以手動修改),可以設置生成的exe的名字

  • COLLECT生成其他部分的輸出文件夾,COLLECT也可以沒有,一般不需要更改,可以設置生成的項目文件夾名稱

 

 

2.修改 spec文件

我們修改默認生成的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項目,如上的spec腳本,將所有項目中的py文件路徑以列表形式寫入Analysis,這里為了說明混合使用了絕對路徑和相對路徑。這里面的所有列表項都必須是py文件!(經過測試,其實只要啟動入口只需要默認生成的main.py就好了,其他的py文件不管是獨立的py文件還是在那個python包下都可以不要,因為入口文件中的模塊加載A.py,A又加載B.py,這其中的導入關系是可以識別的)
  • Analysis類中的pathex定義了打包的主目錄,對於在此目錄下的py文件可以只寫文件名不寫路徑(默認生成的,不用管) 

b) 打包 資源文件(其實就是復制資源到打包后文件夾對應位置)

  • 資源文件包括打包的python項目使用的相關文件,如圖標文件,文本文件等。
  • 對於此類資源文件的打包需要設置Analysis的datas,將非py文件的路徑與存放的文件夾名寫在元組里,如:datas=[(SETUP_DIR+'lib\\icon','lib\\icon'),(SETUP_DIR+'data','data')]
  • datas:
第一個參數:Python中的資源文件等非py類型文件的路徑
第二個參數:打包后路徑,要和路徑中的文件夾名稱相同
  • 例子中的(SETUP_DIR+'lib\\icon','lib\\icon')表示將D:\\install_test\\FASTPLOT\\lib\\icon文件夾以及其中內容打包后還放在打包路徑下的lib\\icon目錄
  • 假如項目某個文件夾打包時是空文件夾,但是運行中需要用到,比如log文件夾,即使將log地址寫進datas中也不會生成,需要自己在代碼里面用到某個文件夾去查看是否存在然后遞歸創建

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中存放打包的結果,可執行文件和其它程序運行的關聯文件都在這個目錄下,dist文件夾里包含了整個項目所需的代碼和環境,可在其他電腦中進行使用

三  pipenv管理的虛擬環境下python項目的打包

當項目不使用默認的pip管理,采用更高級的pipenv管理的時候,是額外的一個虛擬環境,各種依賴都在虛擬幻境里,就需要切換到虛擬環境中操作,話不多說先上圖:

該項目采用最新版 python 3.8.0 

 

 

第一步:管理員方式打開cmd,cd到項目根目錄

cd C:\Users\13154\Desktop\taibaorpa

第二步:創建項目的虛擬環境,如果已經有了,跳過這一步:

pipenv install

pipenv會根據項目的pipfile自動安裝其中記載的所有依賴

第三步:進入虛擬環境

pipenv shell

 

 進入后,路徑最前面會出現虛擬環境

第四步:查看依賴是否安裝成功,也可以不查看

pipenv graph

第五步:安裝pyinstaller

pip install pyinstaller

第六步:生成spec

pyi-makespec -D -c main.py  # 生成的spec文件中console=True,即需要打印到控制台

第七步:修改spec

# -*- mode: python ; coding: utf-8 -*-
import sys
import os.path as osp
sys.setrecursionlimit(5000)
SETUP_DIR = 'C:\\Users\\13154\\Desktop\\taibaorpa\\'

block_cipher = None


a = Analysis(['main.py',
            SETUP_DIR+'asynctask.py',
            SETUP_DIR+'errors.py',
            SETUP_DIR+'file.py',
            SETUP_DIR+'msg.py',
            SETUP_DIR+'msg_pattern.py',
            SETUP_DIR+'pageon.py',
            SETUP_DIR+'report.py',
            SETUP_DIR+'script.py',
            SETUP_DIR+'settings.py',
            SETUP_DIR+'taibaorpa.py',
            SETUP_DIR+'taiboweb.py',
            SETUP_DIR+'webdriverhelper.py',
            SETUP_DIR+'wechat.py',],
             pathex=['C:\\Users\\13154\\Desktop\\taibaorpa'],
             binaries=[],
             datas=[(SETUP_DIR+'driver\\chrome','driver\\chrome'),
             (SETUP_DIR+'log\\screenshot_taiboweb','log\\screenshot_taiboweb'),
             (SETUP_DIR+'log\\screenshot_wechat','log\\screenshot_wechat'),
             (SETUP_DIR+'log\\wechat_files','log\\wechat_files'),
             (SETUP_DIR+'report','report'),
             (SETUP_DIR+'res\\img','res\\img'),
             (SETUP_DIR+'res\\vid','res\\vid'),
             (SETUP_DIR+'README.md','.')],
             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='main',
          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='main')
修改后的spec

第八步:根據spec打包

pyinstaller main.spec

然后打開dist/main文件夾,里面的main.exe就是執行文件,雙擊能正確啟動則打包成功

 

pyinstaller給打包后的文件添加文件版本信息

 

五 Python模塊的打包問題

這里總結了一些遇到的錯誤:pyinstaller打包項目報錯總結

程序調用的很多包,在打包時候可能會出現一些問題,針對這寫問題需要做一些處理才能保證打包的程序正常執行。

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: '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')

5.執行打包命令時報錯 ImportError: No module named 'queue'

  • 原因:尚不清楚
  • 解決方法:如果該模塊你用不到,可以在執行打包命令時用 --hidden-import 不打包進去。如果程序中需要該模塊,在主文件最上面寫上 improt queue

6.打包命令執行成功,但雙擊可執行程序彈出報錯窗口failed to excute script xxx

  • 原因:打包時內部缺少了某個依賴,這時需要看看控制台打印了什么報錯信息,打包時加了 -w 參數的請再打包一次記得去掉 -w以便於查看log信息
  • 現象:基本都是在控制台上發現報錯 No module named 'xxxxx',如 No module named 'queue' 或者 ModuleNotFoundError: No module named 'PyQt5.sip'
  • 解決方法1:同2,如果該模塊你用不到,可以在執行打包命令時用 --hidden-import 不打包進去。如果程序中需要該模塊,在主文件最上面寫上 improt xxxxx。如 import queue 或 import PyQt5.sip
  • 解決方法2:我用pipenv管理的項目,但是在真實環境下打包,打包好后運行出現這個,重新走 第二章 中流程,成功打包

7.凍結打包路徑

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

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

 


免責聲明!

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



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