溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python3快速入門(十八)——PyInstaller打包發(fā)布

發(fā)布時(shí)間:2020-06-14 14:44:11 來源:網(wǎng)絡(luò) 閱讀:3271 作者:天山老妖S 欄目:編程語(yǔ)言

Python3快速入門(十八)——PyInstaller打包發(fā)布

一、PyInstaller簡(jiǎn)介

1、PyInstaller簡(jiǎn)介

PyInstaller是一個(gè)跨平臺(tái)的Python應(yīng)用打包工具,支持 Windows/Linux/MacOS三大主流平臺(tái),能夠把 Python 腳本及其所在的 Python 解釋器打包成可執(zhí)行文件,從而允許最終用戶在無需安裝 Python 的情況下執(zhí)行應(yīng)用程序。
PyInstaller 制作出來的執(zhí)行文件并不是跨平臺(tái)的,如果需要為不同平臺(tái)打包,就要在相應(yīng)平臺(tái)上運(yùn)行PyInstaller進(jìn)行打包。

2、PyInstaller安裝

pip install PyInstaller

二、PyInstaller基礎(chǔ)用法

1、PyInstaller使用

pyinstaller main.py
PyInstaller 最簡(jiǎn)單使用只需要指定作為程序入口的腳本文件。PyInstaller 執(zhí)行打包程序后會(huì)在當(dāng)前目錄下創(chuàng)建下列文件和目錄:
main.spec 文件,其前綴和腳本名相同,指定了打包時(shí)所需的各種參數(shù);
build 子目錄,其中存放打包過程中生成的臨時(shí)文件。warnxxxx.txt文件記錄了生成過程中的警告/錯(cuò)誤信息。如果 PyInstaller 運(yùn)行有問題,需要檢查warnxxxx.txt文件來獲取錯(cuò)誤的詳細(xì)內(nèi)容。xref-xxxx.html文件輸出 PyInstaller 分析腳本得到的模塊依賴關(guān)系圖。
dist子目錄,存放生成的最終文件。如果使用單文件模式將只有單個(gè)執(zhí)行文件;如果使用目錄模式的話,會(huì)有一個(gè)和腳本同名的子目錄,其內(nèi)才是真正的可執(zhí)行文件以及附屬文件。

2、PyInstaller命令行選項(xiàng)

PyInstaller命令行選項(xiàng)可以通過幫助信息查看:
pyinstaller --help
-y | --noconfirm:直接覆蓋輸出文件,而無需提示,在多次重復(fù)運(yùn)行命令時(shí)可避免反復(fù)確認(rèn)。
-D | --onedir:生成包含執(zhí)行文件的目錄(默認(rèn)行為)。
-F | --onefile:生成單一的可執(zhí)行文件,不推薦使用。
-i | --icon [.ico | .exe | .icns]:為 Windows/Mac 平臺(tái)的執(zhí)行文件指定圖標(biāo)。
--version-file [filename]:添加文件版本信息。
-c | --console | --nowindowed:通過控制臺(tái)窗口運(yùn)行程序 并且分配標(biāo)準(zhǔn)輸入/輸出,(默認(rèn)行為)。
-w | --windowed | --noconsole:不創(chuàng)建控制臺(tái)窗口,也不分配標(biāo)準(zhǔn)輸入/輸出,主要用來運(yùn)行 GUI 程序。沒有輸入輸出會(huì)給調(diào)試帶來一定困難,因此即便是 GUI 程序,建議在調(diào)試時(shí)禁用本選項(xiàng),在最終發(fā)布時(shí)再打開。
--add-data [file:dir]:添加數(shù)據(jù)文件。如果有多個(gè)文件需要添加,本選項(xiàng)可以出現(xiàn)多次。參數(shù)的格式為文件名+輸出目錄名,用路徑分隔符分割,在 Windows 下使用?;,其它系統(tǒng)下則使用?:。 如果輸出到和腳本相同的目錄,則使用?.?作為輸出目錄。
--add-binary [file:dir]:添加二進(jìn)制文件,即運(yùn)行程序所需的.exe/.dll/.so 等。

3、單目錄模式

單目錄模式是 PyInstaller 將 Python 程序編譯為同一個(gè)目錄下的多個(gè)文件,其中 xxxx.exe 是程序入口點(diǎn)(xxxx 是腳本文件名稱,可以通過命令行修改)。單目錄模式是 PyInstaller 的默認(rèn)模式,可以自己加上?-D?或者?--onedir?開關(guān)顯式開啟。
單目錄模式打包生成的目錄除可執(zhí)行文件外,還包括 Python 解釋器(PythonXX.dll)、系統(tǒng)運(yùn)行庫(kù)(ucrtbase.dll 以及其它 apixx.dll),以及一些編譯后的 Python 模塊(.pyd 文件)。

4、單文件模式

單文件模式是將整個(gè)程序編譯為單一的可執(zhí)行文件。需要在命令行添加?-F?或者?--onefile?開關(guān)開啟。
Python腳本是解釋型程序,而不是 原生的編譯程序,并不能產(chǎn)生出真正單一的可執(zhí)行文件。如果使用單文件模式,PyInstaller打包生成的是自動(dòng)解壓程序,需要先把所有文件解壓到一個(gè)臨時(shí)目錄(通常名為_MEIxxxx,xxxx是隨機(jī)數(shù)字),再?gòu)呐R時(shí)目錄加載解釋器和附屬文件。程序運(yùn)行完畢后,如果一切正常,會(huì)將臨時(shí)目錄再刪除。
PyInstaller會(huì)對(duì)運(yùn)行時(shí)的Python解釋器修改。如果直接運(yùn)行 Python 腳本,那么sys.frozen?變量不存在,如果通過 PyInstaller 生成的可執(zhí)行文件運(yùn)行,PyInstaller 會(huì)設(shè)置sys.frozen?變量為 True;如果使用單文件模式,sys._MEIPASS?變量包含了PyInstaller 自動(dòng)創(chuàng)建的臨時(shí)目錄名。
單文件模式因?yàn)橛信R時(shí)目錄和解壓文件過程,所以程序啟動(dòng)速度會(huì)比較慢。如果程序運(yùn)行到一半崩潰,則臨時(shí)目錄將沒有機(jī)會(huì)被刪除。

三、PyInstaller規(guī)格文件

PyInstaller 在生成文件的同時(shí)會(huì)創(chuàng)建一個(gè)相應(yīng)的.spec 文件,.spec 文件本質(zhì)上是一個(gè)特殊的 Python 腳本,記錄了生成所需的指令。

1、Spec文件生成

使用pyinstaller [options] xxx.py進(jìn)行打包時(shí),PyInstaller 會(huì)首先根據(jù)選項(xiàng)生成對(duì)應(yīng)的 .spec 文件,然后執(zhí)行 .spec 文件所指定的過程生成最終文件。因此,可以直接指定spec文件執(zhí)行打包過程。
pyinstaller [options] xxx.spec

2、Spec文件格式

單目錄模式生成的spec 文件格式如下:

a = Analysis(...)
pyz = PYZ(...)
exe = EXE(...)
coll = COLLECT(...)

單文件模式生成的spec 文件格式如下:

a = Analysis(...)
pyz = PYZ(...)
exe = EXE(...)

單文件模式是將所有內(nèi)容統(tǒng)一打包到 .exe,而單目錄模式除了生成 .exe 外,還需要拷貝其它附屬文件。
Analysis用于分析腳本的引用關(guān)系,并將所有查找到的相關(guān)內(nèi)容記錄在內(nèi)部結(jié)構(gòu)中,供后續(xù)步驟使用;
PYZ將所有 Python 腳本模塊編譯為對(duì)應(yīng)的 .pyd 并打包;
EXE:將打包后的 Python 模塊及其它文件一起生成可執(zhí)行的文件結(jié)構(gòu);
COLLECT:將引用到的附屬文件拷貝到生成目錄的對(duì)應(yīng)位置。
如果數(shù)據(jù)文件很多導(dǎo)致 Analysis 太長(zhǎng),則可以提取為單獨(dú)的變量。

data_files = [(...)]
a = Analysis(...,
             datas=data_files,
             ...)

可以為數(shù)據(jù)/二進(jìn)制文件指定通配符,從而匹配同一類型的多個(gè)文件。

a = Analysis(...,
             datas=[('media/*.mp3', 'media')],
             ...)

可以將指定文件和指定目錄打包進(jìn)行打包,如下:

a = Analysis(...,
             datas=[('config.ini', '.'), ('data', 'data')],
             ...)

將config.ini文件打包當(dāng)可執(zhí)行文件當(dāng)前目錄下,將data目錄打包到可執(zhí)行文件當(dāng)前目錄下。

四、PyInstaller Hook機(jī)制

1、PyInstaller Hook簡(jiǎn)介

PyInstaller 使用遞歸方法,從入口的腳本文件逐個(gè)分析,獲取依賴模塊。
PyInstaller 能識(shí)別 ctypes、SWIG、Cython 等形式的模塊調(diào)用,但文件名必須為字面值。但PyInstaller 無法識(shí)別動(dòng)態(tài)和調(diào)用,例如?import、exec、eval,以及以變量為參數(shù)的調(diào)用。
當(dāng) PyInstaller 識(shí)別完所有模塊后,會(huì)在內(nèi)部構(gòu)成一個(gè)樹形結(jié)構(gòu)表示調(diào)用關(guān)系圖,調(diào)用關(guān)系在生成目標(biāo)時(shí)也會(huì)一并輸出(xref-xxxx.html 文件)。PYZ 步驟會(huì)將所有識(shí)別到的模塊匯集起來,如果有必要會(huì)編譯成.pyd,然后將文件打包。但仍然存在以下問題:
(1)由于動(dòng)態(tài)模塊調(diào)用未必可以自動(dòng)識(shí)別到,因此不會(huì)打包到文件中,執(zhí)行時(shí)肯定會(huì)出現(xiàn)問。
(2)有些模塊并非是以模塊的形式,而是通過文件系統(tǒng)去訪問 .py 文件,代碼在運(yùn)行時(shí)同樣會(huì)出現(xiàn)問題。
為了解決上述問題,PyInstaller引入了Hooks機(jī)制,對(duì)于兩種問題引入了兩種類型的 Hook。兩種 Hook 主要是按照加載時(shí)間區(qū)分,第一種Hook在 PyInstaller 文檔中沒有明確的命名,是在生成過程中,導(dǎo)入特定模塊時(shí)調(diào)用的,稱為 Import Hook;第二種是Runtime Hook,是在執(zhí)行文件啟動(dòng)期間、加載特定模塊時(shí)調(diào)用的。

2、Import Hooks

PyInstaller 定義的所有 Hook 在 PyInstaller 安裝目錄的 hooks 子目錄下,文件的命名均為 hook-[模塊名].py 的形式,即為 Import Hook。
當(dāng) PyInstaller 生成過程中找到特定的導(dǎo)入模塊,就會(huì)到hooks目錄下查找是否存在對(duì)應(yīng)的Hook,如果存在,則執(zhí)行之。
hook-PyQt5.py文件如下:

import os

from PyInstaller.utils.hooks import collect_system_data_files
from PyInstaller.utils.hooks.qt import pyqt5_library_info, get_qt_binaries

# Ensure PyQt5 is importable before adding info depending on it.
if pyqt5_library_info.version:
    hiddenimports = [
        # PyQt5.10 and earlier uses sip in an separate package;
        'sip',
        # PyQt5.11 and later provides SIP in a private package. Support both.
        'PyQt5.sip'
    ]

    # Collect the ``qt.conf`` file.
    datas = [x for x in
             collect_system_data_files(pyqt5_library_info.location['PrefixPath'],
                                       'PyQt5')
             if os.path.basename(x[0]) == 'qt.conf']

    # Collect required Qt binaries.
    binaries = get_qt_binaries(pyqt5_library_info)

hiddenimports是PyInstaller 用來描述并非通過 import 明確導(dǎo)入,而是通過其它動(dòng)態(tài)機(jī)制加載的模塊。

3、Runtime Hooks

Runtime Hooks均位于 PyInstaller 安裝目錄下的loader\rthooks 子目錄下,并且命名方式是 pyi_rth_[模塊名稱].py(rth 代表 run time hook)。
loader\rthooks.dat內(nèi)容是一個(gè)字典,記錄了系統(tǒng)中所有支持的 Runtime Hooks。rthooks.dat文件如下:

{
    'certifi':    ['pyi_rth_certifi.py'],
    'django':     ['pyi_rth_django.py'],
    'enchant':    ['pyi_rth_enchant.py'],
    'gi':         ['pyi_rth_gi.py'],
    'gi.repository.Gio':    ['pyi_rth_gio.py'],
    'gi.repository.GLib':   ['pyi_rth_glib.py'],
    'gi.repository.GdkPixbuf':    ['pyi_rth_gdkpixbuf.py'],
    'gi.repository.Gtk':    ['pyi_rth_gtk.py'],
    'gi.repository.Gst':    ['pyi_rth_gstreamer.py'],
    'gst':        ['pyi_rth_gstreamer.py'],
    'kivy':       ['pyi_rth_kivy.py'],
    'kivy.lib.gstplayer': ['pyi_rth_gstreamer.py'],
    'matplotlib': ['pyi_rth_mplconfig.py', 'pyi_rth_mpldata.py'],
    'osgeo':      ['pyi_rth_osgeo.py'],
    'pkg_resources':  ['pyi_rth_pkgres.py'],
    'PyQt4':      ['pyi_rth_qt4plugins.py'],
    'PyQt5':      ['pyi_rth_pyqt5.py'],
    'PyQt5.QtWebEngineWidgets': ['pyi_rth_pyqt5webengine.py'],
    'PySide':      ['pyi_rth_qt4plugins.py'],
    'PySide2':      ['pyi_rth_pyside2.py'],
    'PySide2.QtWebEngineWidgets': ['pyi_rth_pyside2webengine.py'],
    '_tkinter':    ['pyi_rth__tkinter.py'],
    'traitlets':  ['pyi_rth_traitlets.py'],
    'twisted.internet.reactor':        ['pyi_rth_twisted.py'],
    'usb':        ['pyi_rth_usb.py'],
    'win32com':   ['pyi_rth_win32comgenpy.py'],
    'multiprocessing': ['pyi_rth_multiprocessing.py'],
    'nltk': ['pyi_rth_nltk.py'],
}

Runtime Hooks 是在執(zhí)行文件運(yùn)行期間執(zhí)行的。PyInstaller 修改了模塊加載機(jī)制,當(dāng)運(yùn)行期間加載任何模塊時(shí),PyInstaller 會(huì)檢查是否有對(duì)應(yīng)的 Runtime Hook,如果有,則運(yùn)行相應(yīng)Hook。因此,Runtime Hooks 是和腳本一起編譯到可執(zhí)行文件中的。
pyi_rth_pyqt5.py文件如下:

import os
import sys

# The path to Qt's components may not default to the wheel layout for
# self-compiled PyQt5 installations. Mandate the wheel layout. See
# ``utils/hooks/qt.py`` for more details.
pyqt_path = os.path.join(sys._MEIPASS, 'PyQt5', 'Qt')
os.environ['QT_PLUGIN_PATH'] = os.path.join(pyqt_path, 'plugins')
os.environ['QML2_IMPORT_PATH'] = os.path.join(pyqt_path, 'qml')

五、錯(cuò)誤調(diào)試

使用PyInstaller進(jìn)行打包時(shí),最常見的錯(cuò)誤是Failed to execute script xxx,通常做法是先使用pyinstaller -c xxx.py將應(yīng)用打包為控制臺(tái)應(yīng)用,在命令行執(zhí)行相應(yīng)可執(zhí)行程序查看錯(cuò)誤輸出,進(jìn)而逐個(gè)排除錯(cuò)誤。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI