国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Python pyinstaller打包exe最完整教程

這篇具有很好參考價(jià)值的文章主要介紹了Python pyinstaller打包exe最完整教程。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

1 簡(jiǎn)介

python提供了多種方法用于將普通的*.py程序文件編譯成exe文件(有時(shí)這里的“編譯”也稱(chēng)作“打包”)。exe文件即可執(zhí)行文件,打包后的*.exe應(yīng)用不用依賴(lài)python環(huán)境,可以在他人的電腦上運(yùn)行。

pyinstaller是一個(gè)第三方模塊,專(zhuān)用于python程序的exe打包。此外python還有一些別的方法進(jìn)行打包,但是pyinstaller打包最強(qiáng)大而且好用。

pyinstaller的官網(wǎng)是:https://pyinstaller.org/

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言??

2 安裝

可以通過(guò)pip進(jìn)行安裝。首先啟動(dòng)cmd,輸入以下內(nèi)容后回車(chē):

pip install pyinstaller

安裝完成后,驗(yàn)證是否成功安裝:

pyinstaller --version

如果顯示找不到“pyinstaller”,請(qǐng)轉(zhuǎn)到最后一章“常見(jiàn)問(wèn)題”

3 原理和打包效果

3.1 原理概述

在開(kāi)始打包前,讀者有必要先了解pyinstaller的打包原理。

如果你只在乎打包結(jié)果而不在乎細(xì)節(jié),你可以跳過(guò)第3章,直接進(jìn)入下面的打包環(huán)節(jié)。但是,當(dāng)你打包時(shí)遇到問(wèn)題時(shí),還是建議你先把打包原理看完,可能你的問(wèn)題會(huì)得到解決。

pyinstaller先讀取你需要打包的python文件,然后搜索其中使用的模塊,然后將所需的模塊以及Python解釋器放到一起,并通過(guò)一些操作構(gòu)建exe,最終形成你的應(yīng)用程序。

3.2 搜索模塊

當(dāng)然,在搜索模塊的時(shí)候必然會(huì)遇到一些問(wèn)題。

pyinstaller只會(huì)搜索import語(yǔ)句,然后根據(jù)import得到的模塊再進(jìn)行搜索。如果編程者使用了一些特殊的導(dǎo)入方式,比如使用__import__()函數(shù),使用importlib里面的導(dǎo)入函數(shù),那么pyinstaller很可能找不到你所需要的模塊。

這時(shí),你可以通過(guò)參數(shù)來(lái)指定你所需要的模塊,也可以使用“鉤子”等等(這是后話)。

3.3 打包效果概述

pyinstaller打包后會(huì)形成一個(gè)文件夾或單個(gè)的exe(可以用參數(shù)指定)。但不論是哪一種情況,都會(huì)包含一個(gè)exe文件,用戶(hù)可以雙擊它運(yùn)行該應(yīng)用程序。

假如你要打包myscript.py,那么打包完成后運(yùn)行這個(gè)myscript.exe,效果就是運(yùn)行myscript.py后的效果。

默認(rèn)情況下,打包會(huì)形成一個(gè)黑色的控制臺(tái)(cmd的樣子),也可以設(shè)置隱藏這個(gè)控制臺(tái)。

這個(gè)控制臺(tái)用于為python提供標(biāo)準(zhǔn)輸入(stdin),標(biāo)準(zhǔn)輸出(stdout),標(biāo)準(zhǔn)錯(cuò)誤(stderr)。也就是說(shuō),這個(gè)控制臺(tái)上顯示了print函數(shù)的輸出,用于接收input函數(shù)的輸入,還會(huì)輸出python的異常。

如果你隱藏了這個(gè)控制臺(tái),程序中的print就無(wú)法顯示(但是不會(huì)報(bào)錯(cuò)),報(bào)錯(cuò)信息也無(wú)法被用戶(hù)直接看到(pyinstaller有一些選項(xiàng)來(lái)控制顯示異常,后文詳解);需要注意的是,此時(shí)不能使用input,否則會(huì)報(bào)錯(cuò):

RuntimeError: input(): lost sys.stdin

python文件有一種后綴名*.pyw,這樣的程序執(zhí)行時(shí)默認(rèn)會(huì)隱藏控制臺(tái)。如果將文件后綴命名為pyw,那么pyinstaller也會(huì)認(rèn)為它隱藏了控制臺(tái),不需要通過(guò)額外的選項(xiàng)來(lái)指定。

當(dāng)你制作GUI程序的時(shí)候,最好選擇隱藏控制臺(tái),來(lái)提升用戶(hù)體驗(yàn)。?

打包后的文件可能會(huì)被反編譯(即通過(guò)exe文件得到原來(lái)的代碼),可以通過(guò)一些方法進(jìn)行加密(后文詳解)。

3.4 打包成單個(gè)文件夾

下面介紹一下打包完成后形成的文件夾。

這個(gè)文件夾的名字是你提供的,一般是你要求打包的python文件的名稱(chēng)。文件夾中包含一個(gè)exe文件,以及其他一些依賴(lài)文件(比如一些dll文件,可能還有你的應(yīng)用所需要的圖片等素材)。你只需要將該文件夾壓縮就能發(fā)給別人運(yùn)行了。

當(dāng)你運(yùn)行里面的exe文件后,pyinstaller其實(shí)只是啟動(dòng)了解釋器,然后通過(guò)解釋器運(yùn)行你的主程序。

優(yōu)點(diǎn)

打包成單個(gè)文件夾的形式便于調(diào)試,因?yàn)槟憧梢郧宄乜吹絧yinstaller將哪些模塊文件放到了文件夾中。

當(dāng)你更改代碼,需要用戶(hù)更新應(yīng)用時(shí),只需要讓用戶(hù)對(duì)于部分內(nèi)容進(jìn)行修改。如果你只修改了主程序,沒(méi)有使用多余的模塊,那么就只需要讓用戶(hù)替換里面的exe文件,而不用全部替換(因?yàn)楦虑昂笫褂玫哪K是一致的,它們都以多文件的形式放到了文件夾中)。

單個(gè)文件夾的狀態(tài)下,程序的啟動(dòng)速度和打包前差不多。

缺點(diǎn)

打包成單個(gè)的文件夾后,里面有大量的依賴(lài)文件,比如*.dll,還有一些文件夾。用戶(hù)從里面找出主程序exe可能需要一點(diǎn)時(shí)間。

3.5 打包成單個(gè)exe

單個(gè)exe模式下,pyinstaller只會(huì)生成一個(gè)單獨(dú)的exe文件,所有的依賴(lài)文件都會(huì)被壓縮到exe文件中。

和上面的文件夾模式類(lèi)似,exe啟動(dòng)后,pyinstaller也是通過(guò)調(diào)用python解釋器來(lái)運(yùn)行主程序的。

優(yōu)點(diǎn)

啟動(dòng)單個(gè)exe非常簡(jiǎn)單,用戶(hù)只需要點(diǎn)擊exe文件就能運(yùn)行,而無(wú)需在一大堆的依賴(lài)文件中找到exe文件。并且在經(jīng)過(guò)壓縮后,這個(gè)exe文件的文件大小會(huì)大大減小。

缺點(diǎn)

單個(gè)exe的啟動(dòng)速度比較慢(通常會(huì)慢幾秒,且只是啟動(dòng)時(shí)的速度,不是運(yùn)行后的速度),這是因?yàn)閜yinstaller會(huì)在這一段時(shí)間中將一些依賴(lài)文件寫(xiě)入到一個(gè)臨時(shí)的文件夾(后文介紹該文件夾的調(diào)用方式)。

如果你希望添加一些附帶文件(比如使用說(shuō)明README),你還需要額外新建文件夾并將其放進(jìn)去。

4 打包

在了解相關(guān)原理后,下面正式進(jìn)入打包環(huán)節(jié)。

本章介紹通過(guò)命令行參數(shù)進(jìn)行打包,這種方式比較初級(jí),適用于一般的打包方式。

4.1 基本語(yǔ)法

打包需要通過(guò)cmd進(jìn)行,語(yǔ)法和大多數(shù)工具一樣。pyinstaller最簡(jiǎn)單的打包方式是:

pyinstaller myscript.py

其中myscript.py是你想要打包的程序。

如果這一步提示找不到myscript.py,請(qǐng)檢查路徑是否正確;如這一步提示找不到pyinstaller工具,請(qǐng)參考最后一章“常見(jiàn)問(wèn)題”。

如果直接傳遞文件名,pyinstaller會(huì)生成一個(gè)spec文件將一些打包參數(shù)放到里面,然后進(jìn)行打包。打包完成后,你會(huì)在你的目錄下找到一個(gè)dist文件夾,里面存儲(chǔ)了打包后的結(jié)果。pyinstaller還會(huì)生成一個(gè)build文件夾并寫(xiě)入一些日志信息。

當(dāng)然,你也可以自己構(gòu)建一個(gè)*.spec文件(后文介紹),然后交給pyinstaller進(jìn)行處理。

4.2 參數(shù)總覽

本節(jié)只是列舉并簡(jiǎn)要介紹常用的參數(shù),并不過(guò)多展開(kāi),將在下面的部分對(duì)于一些重點(diǎn)參數(shù)舉例介紹。

如有不熟悉命令行參數(shù)和命令行使用的讀者可自行搜索,或者參考下面的介紹:

pyinstaller -D -i "icon.ico" myscript.py

調(diào)用命令時(shí),首先給出工具名稱(chēng)(比如上面的 pyinstaller ),然后提供相關(guān)參數(shù),有一些參數(shù)是可選的但不需要附帶任何值(比如上面的 -D ),有一些參數(shù)是必選的(比如上面的 myscript.py ),有一些參數(shù)需要附帶一個(gè)值(比如上面的 -i "icon.ico" )。其中有一些參數(shù)可以簡(jiǎn)寫(xiě)(比如 -i 就是 --icon 的簡(jiǎn)寫(xiě))。

位置參數(shù)

位置參數(shù)在打包時(shí)放在最后,而且無(wú)需指定關(guān)鍵字。

pyinstaller的位置參數(shù)是需要打包的文件路徑,或是spec文件路徑。

可選參數(shù)

下面是比較有用的參數(shù),讀者可以自行了解,也可以跳過(guò)這部分,打包時(shí)用于參考:

參數(shù)名 描述
-D 文件夾模式。在打包完成后生成一個(gè)文件夾,其中包含一個(gè)exe文件和若干依賴(lài)文件(詳見(jiàn)上文)。(默認(rèn))
-F 單文件模式。在打包完成后只會(huì)生成一個(gè)單獨(dú)的exe文件(詳見(jiàn)上文)。
--add-data?<SRC;DEST?or?SRC:DEST>

指定一個(gè)文件夾或文件(非二進(jìn)制),將其嵌入到exe中。

--add-binary?<SRC;DEST?or?SRC:DEST> 和--add-data類(lèi)似,不過(guò)指定的文件夾或文件是二進(jìn)制的

-p?DIR

--paths?DIR

提供一個(gè)路徑進(jìn)行搜索并且導(dǎo)入里面的模塊(不同的路徑使用路徑分隔符os.pathsep分隔開(kāi),或者多次使用這個(gè)參數(shù))。

這可以解決有時(shí)候第三方模塊找不到的問(wèn)題。

--hidden-import MODULENAME

--hiddenimport MODULENAME

需要進(jìn)行額外導(dǎo)入的模塊。當(dāng)pyinstaller在程序中找不到一些模塊時(shí),需要你額外指定。這個(gè)參數(shù)可以多次使用,可以解決一些模塊找不到的問(wèn)題。

--splash?IMAGE_FILE

添加一個(gè)啟動(dòng)畫(huà)面(圖片文件)路徑,在程序運(yùn)行前顯示指定的啟動(dòng)圖片,起到加載提示的效果。
-c, --console, --nowindowed 打包程序運(yùn)行后出現(xiàn)一個(gè)黑色的控制臺(tái)窗口(默認(rèn))
-w, --windowed, --noconsole 打包程序運(yùn)行后隱藏控制臺(tái)窗口

-i?<FILE.ico?or?FILE.exe,ID?or?FILE.icns?or?Image?or?"NONE">

--icon?<FILE.ico?or?FILE.exe,ID?or?FILE.icns?or?Image?or?"NONE">

設(shè)置打包后exe程序的圖標(biāo)(只能在Windows和macOS上使用)

--disable-windowed-traceback

禁用異常提示(只能在Windows和macOS上使用)

--help, -h 打印pyinstaller的幫助信息并退出

4.3?單文件和文件夾模式打包/隱藏控制臺(tái)窗口

下面是一個(gè)程序示例,將創(chuàng)建一個(gè)窗口并顯示一張圖片image.gif和一段提示。讀者無(wú)需了解其代碼細(xì)節(jié)。接下來(lái)將以這個(gè)程序?yàn)槔M(jìn)行一個(gè)簡(jiǎn)單的打包示范。

'''
一個(gè)簡(jiǎn)單的應(yīng)用
'''

import tkinter as tk # 導(dǎo)入tkinter

root = tk.Tk() # 創(chuàng)建窗口
root.title("我的應(yīng)用程序") # 更改標(biāo)題

image = tk.PhotoImage(file="assets/image.gif")
label = tk.Label(root, text="你好,用戶(hù)!", image=image, compound="top")
label.pack() # 顯示圖片

root.mainloop() # 保持窗口運(yùn)行

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

下面是這個(gè)應(yīng)用文件夾的文件層級(jí)結(jié)構(gòu):

- my_app
    - assets
        - image.gif
    - my_app_name.py

由于這個(gè)應(yīng)用不需要進(jìn)行print和input這樣的控制臺(tái)類(lèi)操作,所以我選擇隱藏控制臺(tái)。打開(kāi)cmd并進(jìn)入程序文件所在的文件夾my_app,打包時(shí)添加-w參數(shù):

pyinstaller -w my_app_name.py

接下來(lái)會(huì)出現(xiàn)若干個(gè)INFO提示,如果沒(méi)有錯(cuò)誤,那么打包就成功了。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

完成打包后,生成了build和dist文件夾,以及一個(gè)spec文件;dist文件夾包含打包的結(jié)果,build文件夾中是一些日志信息,spec文件里面是用于打包的配置信息。

接下來(lái)是重要的一步。由于打包時(shí)沒(méi)有綁定任何的資源文件,所以此時(shí)運(yùn)行時(shí)會(huì)報(bào)錯(cuò),提示找不到image.gif。此時(shí),應(yīng)該把程序文件夾下的assets文件夾(參見(jiàn)上方的文件夾層級(jí))復(fù)制到dist文件夾中的程序文件夾,和exe文件位于同一位置。

接下來(lái),再試一下單文件模式的打包,只需添加-F參數(shù):

pyinstaller -w -F my_app_name.py

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

打包后,生成了一個(gè)單個(gè)的my_app_name.exe,而沒(méi)有其他文件。同樣也需要將assets文件夾復(fù)制到與該exe文件的同一位置。?

4.4 資源嵌入exe

經(jīng)常需要復(fù)制文件夾不僅麻煩,而且還無(wú)法防止里面的內(nèi)容被用戶(hù)修改。此時(shí),我們可以使用pyinstaller的--add-data參數(shù),將assets文件夾里面的資源嵌入到exe文件中。

資源嵌入exe只在單文件模式下使用。文件夾模式下,資源文件夾不會(huì)嵌入到exe中,但是會(huì)被復(fù)制到exe所在的文件夾。

使用資源嵌入后,資源文件夾的路徑發(fā)生了變化,我們不能使用一般的相對(duì)路徑來(lái)調(diào)用assets這樣的內(nèi)嵌資源文件夾。

前面已經(jīng)講過(guò),pyinstaller單文件模式下的exe啟動(dòng)后,會(huì)將嵌入的資源文件放到一個(gè)臨時(shí)的文件夾中,這個(gè)文件夾的名字不是固定的,叫做_MEIxxxxx,其中xxxxx是隨機(jī)數(shù)。這個(gè)文件夾的路徑在打包后會(huì)被放到sys._MEIPASS這個(gè)變量里面,只需要調(diào)用sys._MEIPASS就可以獲得這個(gè)路徑文件夾。

于是,我們通過(guò)以下函數(shù)返回正確的路徑:

def get_path(relative_path):
    try:
        base_path = sys._MEIPASS # pyinstaller打包后的路徑
    except AttributeError:
        base_path = os.path.abspath(".") # 當(dāng)前工作目錄的路徑

    return os.path.normpath(os.path.join(base_path, relative_path)) # 返回實(shí)際路徑

這個(gè)函數(shù)通過(guò)一個(gè)相對(duì)的路徑返回實(shí)際的絕對(duì)路徑。

需要注意:sys._MEIPASS這個(gè)屬性只有在打包成exe后才被創(chuàng)建,以py代碼執(zhí)行的時(shí)候這個(gè)屬性是不存在的,所以要通過(guò)try...except...代碼塊捕獲異常。如果不是pyinstaller模式,那么就使用py文件所在的文件夾的路徑作為基本路徑。我們不必?fù)?dān)心這個(gè)函數(shù)的工作原理(雖然者不難理解),這個(gè)函數(shù)可以直接拿來(lái)用(是一位叫做davidpendergast的大佬寫(xiě)的)。

于是,我們將代碼改成這樣(省略了部分內(nèi)容):

...

import sys
import os

def get_path(relative_path):
    try:
        base_path = sys._MEIPASS
    except AttributeError:
        base_path = os.path.abspath(".")

    return os.path.normpath(os.path.join(base_path, relative_path))

...

image = tk.PhotoImage(file=get_path("assets/image.gif"))
...

接下來(lái)進(jìn)行打包:

pyinstaller -w -F --add-data assets;assets my_app_name.py

打包完成后會(huì)生成一個(gè)包含嵌入資源的單獨(dú)的exe,無(wú)需將資源文件放到同一文件夾下也能正常運(yùn)行。

--add-data的參數(shù)由源文件名src和目標(biāo)文件名dest組成。路徑的源文件名和目標(biāo)文件名用文件分隔符進(jìn)行分隔,源文件名是該文件或文件夾的原本的路徑,目標(biāo)文件名是該文件夾嵌入到exe后的放入的文件夾名。

文件分隔符:在Windows系統(tǒng)上是分號(hào),大部分unix系統(tǒng)上是冒號(hào),可以通過(guò)os.pathsep來(lái)查看當(dāng)前系統(tǒng)上的文件分隔符。例如:

>>> import os
>>> os.pathsep
';'

比如--add-data "assets;assets"就表示將原本assets里面的所有文件,放入打包后的assets文件夾。再比如--add-data "assets/*.mp3;music"表示將原本assets里面的所有mp3文件,放入打包后的music文件夾。?

4.5 更改圖標(biāo)

打包完成后,默認(rèn)的程序圖標(biāo)是一個(gè)“蛇”形,但我們也可以進(jìn)行更改。(根據(jù)官方文檔,該功能只能在Windows和macOS上使用)

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

--icon或-i參數(shù)用于設(shè)置圖標(biāo),該參數(shù)的值默認(rèn)為"NONE",表示使用默認(rèn)的圖標(biāo);也可以指定為一個(gè)*.ico格式的Windows圖標(biāo)文件路徑;*.icns的Mac圖標(biāo)文件路徑;或者一個(gè)其他圖片文件(需安裝pillow模塊,會(huì)通過(guò)pillow模塊將其轉(zhuǎn)換成標(biāo)準(zhǔn)的ico/icns格式)。

首先添加一個(gè)圖標(biāo)文件。圖標(biāo)文件在Windows上格式為*.ico,Mac上是*.icns。

- my_app
    - assets
        - image.gif
    - my_app_name.py
    - icon.ico

這個(gè)圖標(biāo)文件其實(shí)放在哪里都可以,因?yàn)榇虬瓿珊笃鋵?shí)它也相當(dāng)于嵌入了exe。但為了方便,還是把它放到同一文件夾下比較好。

pyinstaller -i icon.ico my_app_name.py

為了方便看,之前設(shè)置的-w, -F這些選項(xiàng)都省略了。最后生成了一個(gè)圖標(biāo)與icon.ico相一致的exe。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

4.6 啟動(dòng)畫(huà)面(閃屏)

pyinstaller單文件模式啟動(dòng)速度較慢,所以可能需要一個(gè)啟動(dòng)畫(huà)面(閃屏)進(jìn)行過(guò)渡,提示用戶(hù)正在進(jìn)行加載。這個(gè)啟動(dòng)畫(huà)面可以是單張圖片,也可以是文本(默認(rèn)情況下文本禁用,使用方式參見(jiàn)第5章)。

這個(gè)啟動(dòng)畫(huà)面的實(shí)現(xiàn)基于Tcl/Tk(和python tkinter模塊一樣),打包時(shí)會(huì)附帶約1.5MB的額外文件來(lái)支持這個(gè)功能。

支持閃屏,需要先準(zhǔn)備一張圖片,必須是PNG格式(如果你安裝了pillow模塊,可以用pillow模塊支持的其他格式)。然后,在打包時(shí)加上--splash參數(shù),并傳入圖片路徑。

pyinstaller --splash splash.png my_app_name.py

控制閃屏可以通過(guò)pyi_splash模塊,這個(gè)模塊和上一節(jié)的sys._MEIPASS屬性一樣,在沒(méi)有通過(guò)pyinstaller打包成exe后是不起作用的,所以必須帶上try...except...代碼。

pyi_splash.close()方法用于關(guān)閉閃屏。一般放在程序開(kāi)頭即可,因?yàn)橹灰\(yùn)行到程序開(kāi)頭,說(shuō)明pyinstaller的加載就基本完成了。

于是,在程序開(kāi)頭部分添加以下代碼:

try:
    import pyi_splash
    pyi_splash.close()
except ImportError:
    pass

如果不用這段代碼進(jìn)行關(guān)閉,那么閃屏將一直顯示。

打包后,閃屏效果如下。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

至于pyi_splash還有一個(gè)update_text方法,用于在閃屏畫(huà)面上顯示加載文本,將在5.7節(jié)介紹。?

4.7 禁用異常提示

--disable-windowed-traceback參數(shù)用于禁用異常提示。如果不添加這個(gè)參數(shù),將會(huì)在非控制臺(tái)程序出錯(cuò)(似乎僅限非致命的錯(cuò)誤)時(shí)彈出一個(gè)窗口報(bào)告異常信息(注意:僅在隱藏控制臺(tái)模式下彈出異常報(bào)告窗口)。為了測(cè)試,我在代碼第一行添加了raise Exception,運(yùn)行打包后的exe后效果如圖所示。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

5 使用Spec文件

當(dāng)你調(diào)用以上的打包方式時(shí),會(huì)在腳本的文件夾下生成一個(gè)*.spec文件。

*.spec文件包含了打包需要使用的所有配置信息。直接在命令行中將*.spec文件路徑傳給pyinstaller,也可以進(jìn)行打包。比如:

pyinstaller my_app_name.spec

(其中my_app_name.spec是根據(jù)my_app_name.py生成的Spec文件)?

這樣,當(dāng)你多次打包同一個(gè)項(xiàng)目時(shí),就無(wú)需每次都傳入那么多參數(shù),只需要傳入*.spec文件的路徑即可。

*.spec文件也比較好處理,直接使用python編輯器或記事本就能編輯。

5.1 生成Spec文件

使用pyi-makespec工具可以根據(jù)pyinstaller的命令行參數(shù)生成Spec文件。用法很簡(jiǎn)單,在原先使用pyinstaller的打包命令中,把"pyinstaller"換成"pyi-makespec"就可以生成一個(gè)Spec文件。例如:

pyi-makespec -w -F --add-data assets;assets my_app_name.py

要更改Spec文件的生成路徑,可以指定參數(shù)--specpath。

如果報(bào)錯(cuò)提示找不到pyi-makespec,轉(zhuǎn)到最后一章:常見(jiàn)問(wèn)題。

當(dāng)你使用*.spec文件進(jìn)行pyinstaller打包時(shí),大部分的打包參數(shù)都不可用,需要預(yù)先在*.spec文件中預(yù)先設(shè)定。

pyinstaller會(huì)將*.spec里面的內(nèi)容當(dāng)做代碼執(zhí)行。單文件模式和文件夾模式的*.spec文件略有不同。

下面是一個(gè)*.spec文件(單文件模式打包)的例子。

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


block_cipher = None


a = Analysis(
    ['my_app_name.py'],
    pathex=[],
    binaries=[],
    datas=[('assets', 'assets')],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    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,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='my_app_name',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=False,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

下面是一個(gè)文件夾模式的*.spec文件的例子:

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


block_cipher = None


a = Analysis(
    ['my_app_name.py'],
    pathex=[],
    binaries=[],
    datas=[('assets', 'assets')],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    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='my_app_name',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=False,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)
coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='my_app_name',
)

這里面包含一些特殊的類(lèi),比如Analysis, PYZ, EXE等,文件夾模式下還多了一個(gè)COLLECT類(lèi)。只有當(dāng)pyinstaller運(yùn)行時(shí)才會(huì)被定義,很顯然你不能在python解釋器中直接調(diào)用它們。?這些類(lèi)的參數(shù)與pyinstaller的命令行參數(shù)并不一樣。

接下來(lái)將針對(duì)Spec文件中的這些對(duì)象進(jìn)行介紹

5.2 Analysis對(duì)象

Analysis類(lèi)包含一些分析信息,它分析模塊的導(dǎo)入以及一些依賴(lài)文件。

這個(gè)類(lèi)的常用參數(shù)介紹如下。

參數(shù)名 默認(rèn)值 描述 (常用參數(shù))示例
scripts 必選參數(shù),無(wú)默認(rèn)值 需要分析的文件路徑列表(一般就是需要打包的文件) ["myscript.py"]
pathex None 需要額外進(jìn)行分析模塊導(dǎo)入的文件(夾)路徑,包含命令行--path參數(shù)指定內(nèi)容 ["C:/Python310/Lib/site-packages", "C:/my_module]
binaries None 需要嵌入的二進(jìn)制文件列表,包含命令行--add-binary參數(shù)指定內(nèi)容
datas None 需要嵌入的非二進(jìn)制文件(夾),包含命令行--add-data參數(shù)指定內(nèi)容 [("assets", "assets"), ("music/*.mp3", "music")]
hiddenimport None 需要額外導(dǎo)入的模塊列表 ["module1", "module2"]
hookspath None 鉤子文件路徑列表(鉤子文件用于配置一些模塊特殊的導(dǎo)入,后文詳解)
hooksconfig None 一個(gè)字典,包含鉤子的配置信息
excludes None 需要被忽略,不進(jìn)行導(dǎo)入的模塊列表
runtime_hooks None 運(yùn)行時(shí)的鉤子列表,指定為一系列文件名
noarchive False 如果設(shè)為T(mén)rue,則不會(huì)將源代碼放到一個(gè)存檔中進(jìn)行存儲(chǔ),而是作為多個(gè)單獨(dú)的文件

在完成分析后,需要將一些屬性傳遞給PYZ類(lèi)。Analysis對(duì)象包含了以下屬性,你可以不必了解它們:

屬性名 描述
scripts 同參數(shù)中的scripts
pure 需要一起打包的純python模塊
pathex 同參數(shù)中的pathex
binaries 同參數(shù)中的binaries
datas 同參數(shù)中的datas

5.3 PYZ對(duì)象

完成分析后,將Analysis對(duì)象的一些屬性傳遞給PYZ類(lèi)。PYZ相當(dāng)于一個(gè)壓縮包,里面儲(chǔ)存了所有的依賴(lài)文件。

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

5.4 EXE對(duì)象

定義PYZ對(duì)象后,接下來(lái)需要定義EXE對(duì)象,也就是可執(zhí)行文件對(duì)象。

不同打包模式(單文件或文件夾)的EXE對(duì)象參數(shù)略有不同。其中常用參數(shù)如下:

參數(shù) 默認(rèn)值 描述 (常用參數(shù))示例
console True 是否顯示控制臺(tái),相當(dāng)于命令行-w參數(shù)
disable_windowed_traceback False 是否禁用異常提示,相當(dāng)于命令行--disable-windowed-traceback參數(shù)
name None 可執(zhí)行文件的名稱(chēng)。在Windows上會(huì)自動(dòng)添加".exe"后綴 "my_app_name"
icon None 可執(zhí)行文件的圖標(biāo)路徑 "icon.ico"

5.5 COLLECT對(duì)象(僅-D文件夾模式)

使用文件夾模式打包時(shí)還會(huì)有一個(gè)COLLECT對(duì)象,該對(duì)象用于創(chuàng)建文件夾。它有一個(gè)常用的關(guān)鍵字參數(shù)name,表示文件夾的名稱(chēng)。

5.6 Bundle對(duì)象(僅macOS系統(tǒng))

如果你要在macOS上創(chuàng)建應(yīng)用程序,且你的應(yīng)用程序是無(wú)控制臺(tái)的,那么在exe構(gòu)建完成之后還需要添加一些代碼。

app = BUNDLE(exe,
             name='my_app_name.app',
             icon="icon.ico",
             bundle_identifier=None)

5.7 Splash對(duì)象

如果你想要在應(yīng)用中添加啟動(dòng)畫(huà)面(圖片和文本都可以),需要在Spec文件中額外添加一個(gè)Splash對(duì)象進(jìn)行控制。

在分析完代碼后,創(chuàng)建Splash對(duì)象:

a = Analysis(...)

splash = Splash('splash.png',
                binaries=a.binaries,
                datas=a.datas,
                text_pos=(10, 50),
                text_size=12,
                text_color='black')

然后在EXE中綁定splash對(duì)象。注意:?jiǎn)挝募J胶臀募A模式方式略有不同。

以下是單文件模式綁定splash對(duì)象的方法。

splash = Splash(...)

exe = EXE(pyz,
          a.scripts,
          splash,                   # <-- both, splash target
          splash.binaries,          # <-- and splash binaries
          ...)

以下是文件夾模式的方法。

splash = Splash(...)

exe = EXE(pyz,
          splash,                   # <-- splash target
          a.scripts,
          ...)
coll = COLLECT(exe,
               splash.binaries,     # <-- splash binaries
               ...)

下面介紹Splash對(duì)象的一些參數(shù)。注意:由于Splash窗口基于Tcl/Tk(和python tkinter一樣),所以里面有一些用法與Tcl/Tk(tkinter)的用法很像,但不重要。

參數(shù) 默認(rèn)值 描述 (常用參數(shù))示例
image_file 必選參數(shù),無(wú)默認(rèn)值 圖片文件路徑,必須是PNG格式(如果你安裝了pillow模塊,可以用pillow模塊支持的其他格式) "splash.png"
binaries 必選參數(shù),無(wú)默認(rèn)值 Analysis對(duì)象的binaries屬性
datas 必選參數(shù),無(wú)默認(rèn)值 Analysis對(duì)象的datas屬性
text_pos None 閃屏文本相對(duì)于閃屏圖片的顯示位置(是一個(gè)(x, y)元組,錨點(diǎn)為文本左下角)。如果不指定,則禁用文本顯示 (500, 400)
text_size 12 文本大小
text_font "TkDefaultFont" 文本使用的字體(必須是系統(tǒng)上安裝了的字體),如果不指定則設(shè)為系統(tǒng)默認(rèn)字體 "宋體"
text_color "black" 文本顏色,顏色格式可以是顏色名稱(chēng)字符串或者十六進(jìn)制顏色字符串,如"#ff00ff"(注意:不支持(r, g, b)元組形式)
text_default "Initializing" 默認(rèn)顯示的文本(后面可以用pyi_splash.update_text來(lái)更新顯示的文本) "加載中……"
max_img_size (760, 480) 最大閃屏圖片尺寸。如果超出尺寸,那么閃屏圖片將會(huì)被按縱橫比縮放,容納到該尺寸中。可以設(shè)為None不縮放
always_on_top True 閃屏窗口是否置頂,如果置頂,其位于其他窗口之上
rundir "__splash" 設(shè)置運(yùn)行閃屏?xí)r,用于存放一些相關(guān)文件的文件夾名稱(chēng)。使用這個(gè)參數(shù)主要是為了避免命名沖突,一般不會(huì)使用

下面就以一個(gè)示例來(lái)演示Splash的文本顯示。使用的代碼還是上一章節(jié)使用的。

在開(kāi)頭添加以下代碼:

try:
    import pyi_splash
    import time

    for i in range(100):
        text = f"加載中……進(jìn)度{i}%"
        time.sleep(0.1) # 模擬一個(gè)速度比較慢的加載過(guò)程
        
        pyi_splash.update_text(text) # 更新顯示的文本

    pyi_splash.close() # 關(guān)閉閃屏
            
except ImportError:
    pass

然后通過(guò)pyi-makespec生成對(duì)應(yīng)的Spec文件:

pyi-makespec -w -F --add-data assets;assets --splash splash.png my_app_name.py

由于Splash的文本顯示只能在Spec文件中進(jìn)行配置,所以我們先打開(kāi)my_app_name.spec,將Splash對(duì)象的代碼進(jìn)行修改,如下所示:

splash = Splash(
    'splash.png',
    binaries=a.binaries,
    datas=a.datas,
    text_pos=(30, 270),
    text_size=12,
    minify_script=True,
    always_on_top=True,
)

然后進(jìn)行打包:

pyinstaller my_app_name.spec

運(yùn)行效果如下:

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

可以看到,首先顯示文本被設(shè)定為加載的各個(gè)依賴(lài)文件,然后變成update_text中自己設(shè)定的加載內(nèi)容。

5.8 多包捆綁(打包多個(gè)exe)

有些產(chǎn)品由幾個(gè)不同的應(yīng)用程序組成,每個(gè)應(yīng)用程序可能依賴(lài)于一組通用的第三方庫(kù),或者以其他方式共享一部分代碼。在打包這樣的產(chǎn)品時(shí),如果單獨(dú)對(duì)待每個(gè)應(yīng)用程序,將其與所有依賴(lài)項(xiàng)捆綁在一起,那就太可惜了,因?yàn)檫@意味著要存儲(chǔ)代碼和庫(kù)的副本。

此時(shí),我們可以使用多包特性來(lái)捆綁一組可執(zhí)行應(yīng)用程序,以便它們共享庫(kù)的單個(gè)副本。我們可以在單文件或單文件夾應(yīng)用程序中做到這一點(diǎn)。

比如有兩個(gè)應(yīng)用都使用了tkinter模塊,且這兩個(gè)應(yīng)用相關(guān),需要在發(fā)布時(shí)放到一起(比如一個(gè)應(yīng)用專(zhuān)門(mén)用于圖片剪裁,另外一個(gè)專(zhuān)門(mén)用于圖片濾鏡,它們可能共用了部分功能)。如果分別打包,那么每個(gè)應(yīng)用都會(huì)包含一個(gè)tkinter模塊的依賴(lài)文件,而且都儲(chǔ)存相同的內(nèi)容,這就很浪費(fèi)存儲(chǔ)空間。如果用多包捆綁的話,只會(huì)有一個(gè)tkinter模塊的依賴(lài)文件,兩個(gè)應(yīng)用都可以調(diào)用相同的依賴(lài)。

文件夾模式的多包捆綁

如果采用文件夾模式,想要捆綁多個(gè)應(yīng)用程序,那么只需要共享一個(gè)COLLECT對(duì)象。假如有hello1.py, hello2.py,將這兩個(gè)應(yīng)用進(jìn)行捆綁,可以將它們的Spec文件進(jìn)行一些組合。

首先通過(guò)pyi-makespec分別生成hello1.py, hello2.py的Spec文件。

然后將其中的Analysis, PYZ, EXE, Splash等對(duì)象分別以不同的變量名放入同一個(gè)Spec文件,然后將它們的COLLECT對(duì)象組合起來(lái)。

hello1_a = Analysis(['hello1.py'], ...)
hello1_pyz = PYZ(hello1_a.pure, hello1_a.zipped_data, ...)
hello1_exe = EXE(hello1_pyz,
          hello1_a.scripts, 
          ...)

hello2_a = Analysis(['hello2.py'], ...)
hello2_pyz = PYZ(hello2_a.pure, hello2_a.zipped_data, ...)
hello2_exe = EXE(hello2_pyz,
          hello2_a.scripts, 
          ...)

coll = COLLECT(hello1_exe,
               hello1_a.binaries,
               hello1_a.zipfiles,
               hello1_a.datas,

               hello2_exe,
               hello2_a.binaries,
               hello2_a.zipfiles,
               hello2_a.datas,
               ...
               name='hello')

這樣,將會(huì)生成同一個(gè)文件夾,該文件夾下包含兩個(gè)文件hello1.exe, hello2.exe。 它們共享一部分的依賴(lài)文件。

單文件模式的多包捆綁

單文件模式下,多包捆綁會(huì)生成多個(gè)單獨(dú)的exe,其中一個(gè)exe包含它們共有的依賴(lài)文件。

比如打包hello1.py和hello2.py,設(shè)置hello1包含共有的依賴(lài)文件,最后生成hello1.exe, hello2.exe。生成的hello1.exe由于包含兩個(gè)exe共有的依賴(lài)文件,其文件大小會(huì)大于hello2.exe。

運(yùn)行hello1.exe時(shí)與單獨(dú)打包效果相同。但是運(yùn)行hello2.exe時(shí),它會(huì)在hello1.exe中搜索它需要的依賴(lài)文件,速度會(huì)稍慢。

如果將hello2.exe移動(dòng)到別的地方,或者將hello1.exe改名,那么hello2.exe將無(wú)法運(yùn)行,因?yàn)樗也坏絟ello1.exe,從而無(wú)法找到所需的依賴(lài)文件。

以下是hello1.py和hello2.py兩個(gè)程序文件,將以它們?yōu)槔M(jìn)行打包。

# hello1.py
while True:
    input("hello1")

# hello2.py
while True:
    input("hello2")

首先通過(guò)pyi-makespec生成對(duì)應(yīng)的Spec文件。完成后,將兩個(gè)Spec文件的Analysis類(lèi)匯總到一個(gè)文件中,并進(jìn)行改名。

a1 = Analysis(
    ['hello1.py'],
    ...
)

a2 = Analysis(
    ['hello2.py'],
    ...
)

接下來(lái)在下面調(diào)用MERGE函數(shù)。這個(gè)函數(shù)會(huì)分析兩個(gè)文件中重復(fù)的依賴(lài)項(xiàng),將結(jié)果放到分析類(lèi)的dependencies屬性中。MERGE中位于第一個(gè)的程序?qū)?huì)包含共有的依賴(lài)項(xiàng)。

MERGE((a1, "hello1", "hello1"), (a2, "hello2", "hello2"))

然后將兩個(gè)文件的ZIP和EXE進(jìn)行匯總。匯總時(shí)需要額外向EXE類(lèi)傳遞一個(gè)參數(shù)Analysis.dependencies。

pyz1 = PYZ(...)
exe1 = EXE(pyz1,

    a1.dependencies, ####

    a1.scripts, 
    a1.binaries,
    a1.zipfiles,
    a1.datas, ...)

pyz2 = PYZ(...)
exe2 = EXE(
    pyz2,

    a2.dependencies, ####

    a2.scripts,
    a2.binaries,
    a2.zipfiles,
    a2.datas, ...)

?保存文件,然后通過(guò)pyinstaller打包。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

最后生成兩個(gè)文件,可以看到hello1.exe的文件大小比hello2.exe大了很多,這是由于hello1.exe中包含了它們共有的依賴(lài)庫(kù)。如果不使用多包捆綁,而是分別單獨(dú)進(jìn)行打包,那么兩個(gè)文件的大小將都會(huì)超過(guò)5000KB。

6 鉤子

有一些特殊的模塊,它們存在一些特殊的依賴(lài)文件(比如ico, json等等)。而pyinstaller的導(dǎo)入分析無(wú)法檢測(cè)到這些特殊的依賴(lài)文件,這就導(dǎo)致運(yùn)行后出現(xiàn)問(wèn)題。于是,pyinstaller引入了“鉤子”。鉤子文件其實(shí)就是一種python文件,后綴名為*.py即可(和Spec文件的實(shí)質(zhì)是一樣的)。鉤子文件中指定了某個(gè)特殊模塊所需要的所有依賴(lài)文件。通過(guò)傳遞鉤子文件,pyinstaller就能找到那些“隱藏”的依賴(lài)文件。

雖然鉤子文件的作用也可以被--hiddenimport, --datas這些命令行參數(shù)替代,但是使用鉤子顯然更加方便。

pyinstaller有一些內(nèi)置的“鉤子”,提供了一些常用模塊的鉤子文件,它們包含Django, pickle, pyqt, scipy等等。

鉤子文件的常用命名格式是:hook-module.py(其中module是模塊名)。(當(dāng)然你也可以按自己喜好命名)

6.1?鉤子文件中的全局變量

鉤子文件中可以包含以下全局變量(有一些變量可以不被寫(xiě)在文件中):

屬性 描述 (常用屬性)示例
hiddenimports 需要額外導(dǎo)入的模塊列表,相當(dāng)于命令行--hidden-import參數(shù) ["sys", "pygame.mixer"]
excludedimports 需要被排除,不被自動(dòng)導(dǎo)入的模塊列表(如果有一些模塊在其他地方被導(dǎo)入,那么仍然會(huì)導(dǎo)入它) ["tkinter"]
datas 需要備添加的非二進(jìn)制文件或文件夾,相當(dāng)于命令行--add-data參數(shù) [('/usr/share/icons/education_*.png', 'assets') ]
binaries 需要備添加的二進(jìn)制文件,相當(dāng)于命令行--add-binary參數(shù)

以下是一個(gè)鉤子文件的示例:

hiddenimports = ["re", "os"]
datas = [("assets", "assets)]

6.2 PyInstaller.utils.hooks

pyinstaller提供了一些方法用于鉤子文件的制作。這些方法位于PyInstaller.utils.hooks模塊。首先需要在鉤子文件導(dǎo)入該模塊。(注意pyinstaller的P和I是大寫(xiě)的,這是pyinstaller作為模塊時(shí)的名稱(chēng))

import PyInstaller.utils.hooks as hooks

下面介紹該模塊中的常用函數(shù)。


is_module_satisfies(requirements, version=None, version_attr='__version__')

檢驗(yàn)?zāi)K版本是否達(dá)到requirements的要求,返回一個(gè)布爾值。關(guān)于requirements的相關(guān)格式,詳見(jiàn)PEP 440。version_attr參數(shù)指定該模塊中版本屬性的名稱(chēng),默認(rèn)是"__version__"。

下面是一些requirements的例子:

"pygame >= 2.2.1dev1" # 大于2.1.1dev1版本的pygame模塊
"PIL == 2.9.*"    # 版本以2.9.開(kāi)頭的PIL模塊
"sphinx >= 1.3.1; sqlalchemy != 0.6" # 同時(shí)滿(mǎn)足兩個(gè)要求

collect_submodules(package, filter=<function <lambda>>, on_error='warn once')

返回一個(gè)模塊的所有子模塊。filter是一個(gè)篩選函數(shù),接收模塊名作為參數(shù),返回一個(gè)布爾值表示是否要加入這個(gè)模塊到返回值中。on_error表示篩選出現(xiàn)異常時(shí)的處理,可以是:"raise"(拋出異常并停止pyinstaller構(gòu)建),"warn"(只拋出警告,不停止pyinstaller構(gòu)建),"warn once"(只警告一次,后續(xù)與之相同的警告被忽略),"ignore"(忽略,不拋出任何警告或異常)

例如:

# 收集Sphinx的所有子模塊(名字中不包含test)
hiddenimports = collect_submodules(
    "Sphinx", filter=lambda name: 'test' not in name)

collect_data_files(package, include_py_files=False, subdir=None, excludes=None, includes=None)?

返回一個(gè)模塊使用的所有非二進(jìn)制文件。include_py_files表示返回的文件列表中是否應(yīng)該含有*.py格式的文件。subdir是相對(duì)于要搜索的包的子目錄。excludes, includes分別是需要被排除和被包含的文件列表,可以指定它們來(lái)判斷是否要保留或移除某些格式的文件。

collect_dynamic_libs(package, destdir=None, search_patterns=['*.dll', '*.dylib', 'lib*.so'])

返回一個(gè)模塊使用的所有二進(jìn)制動(dòng)態(tài)庫(kù)文件。

collect_all(package_name, include_py_files=True, filter_submodules=None, exclude_datas=None, include_datas=None, on_error='warn once')

相當(dāng)于上面的collect前綴的幾個(gè)函數(shù)的綜合。例如:

datas, binaries, hiddenimports = collect_all('my_module_name')

使用hooks模塊可以更加方便地制作鉤子。

6.3 為自己的模塊提供鉤子

如果自己創(chuàng)建的模塊需要鉤子,那么可以自己定義一個(gè)文件,并儲(chǔ)存到自己的模塊中。

如果你有一個(gè)名為module_name的模塊文件夾,首先在自己模塊的setup.cfg中(與setuptools模塊相關(guān),可自行搜索)添加如下代碼(注意里面的module_name):

[options.entry_points]
pyinstaller40 =
  hook-dirs = module_name.__pyinstaller:get_hook_dirs
  tests     = module_name.__pyinstaller:get_PyInstaller_tests

然后在module_name中添加名字為_(kāi)_pyinstaller的文件夾(與上面hook-dirs和tests里面的命名相一致即可)。

最后可以在__pyinstaller文件夾中添加hook文件。

7 反編譯與加密

pyinstaller制作的應(yīng)用,可能會(huì)被反編譯(即根據(jù)生成的exe得到這個(gè)程序的源代碼)。同時(shí),也有一些方法來(lái)預(yù)防反編譯,或者增加反編譯的難度。

需要注意的是,反編譯代碼的結(jié)果大多數(shù)時(shí)候并不準(zhǔn)確,只能得到大概的代碼,可能需要后期處理。

7.1 通過(guò)pyinstxtractor進(jìn)行反編譯

pyinstxtractor是專(zhuān)門(mén)針對(duì)pyinstaller的反編譯工具(也就是說(shuō),其他的打包工具,比如py2exe,cx_Freeze打包的程序無(wú)法被這個(gè)工具反編譯,需要通過(guò)別的反編譯工具)。

下載工具

首先通過(guò)以下鏈接下載pyinstxtractor:?

PyInstaller Extractor download | SourceForge.net

也可以通過(guò)github下載(推薦上面的方法,畢竟github訪問(wèn)較慢):

GitHub - extremecoders-re/pyinstxtractor: PyInstaller Extractor

下載完成后,得到pyinstxtractor.py。

還需要下載pycdc,鏈接如下:

pycdc.exe · Python-ZZY - Gitee.com

想要反編譯一個(gè)pyinstaller打包的應(yīng)用,流程是這樣的:

  1. 先用pyinstxtractor將*.exe文件反編譯成*.pyc文件
  2. 用十六進(jìn)制編輯器修改*.pyc文件中的magic number
  3. 使用pycdc工具將*.pyc轉(zhuǎn)換為最終的*.py文件

下面還是以這個(gè)程序?yàn)槔鳛檠菔荆?/p>

'''
一個(gè)簡(jiǎn)單的應(yīng)用
'''

import tkinter as tk # 導(dǎo)入tkinter

root = tk.Tk() # 創(chuàng)建窗口
root.title("我的應(yīng)用程序") # 更改標(biāo)題

image = tk.PhotoImage(file="assets/image.gif")
label = tk.Label(root, text="你好,用戶(hù)!", image=image, compound="top")
label.pack() # 顯示圖片

root.mainloop() # 保持窗口運(yùn)行

為了方便演示,采用單文件模式進(jìn)行打包:pyinstaller -F my_app_name.py。打包完成后,將assets文件夾放到exe所在文件夾中。

反編譯exe

下面進(jìn)入反編譯環(huán)節(jié)。進(jìn)入exe的文件夾,將下載的pyinstxtractor.py放到*.exe所在文件夾下。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言?

在exe所在文件夾啟動(dòng)cmd,并輸入以下命令:

python pyinstxtractor.py my_app_name.exe

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言?

運(yùn)行完成后,可以看到生成了一個(gè)xxxx.exe_extracted的文件夾

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言?

進(jìn)入此文件夾,可以找到一個(gè)文件名和應(yīng)用名稱(chēng)相同,但是沒(méi)有后綴的文件,這就是得到的*.pyc文件(雖然生成的時(shí)候沒(méi)有后綴,不過(guò)這并不妨礙它本身的文件類(lèi)型)。xxxx.exe_extracted文件夾中的其他文件則是一些依賴(lài)程序文件,等等。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言?

添加magic number

接下來(lái)一步很關(guān)鍵,需要在my_app_name這個(gè)文件中添加magic number,也就是一些python版本相關(guān)的信息。這里需要使用十六進(jìn)制編輯器(有很多,不一一介紹了,sublimetext就可以用來(lái)編輯)?

magic number前面一部分與python版本有關(guān),可以通過(guò)下面的代碼查看當(dāng)前python版本所對(duì)應(yīng)的magic number的十六進(jìn)制形式:

import importlib
print(importlib.util.MAGIC_NUMBER.hex())

?pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

如果不知道編寫(xiě)這個(gè)應(yīng)用所使用的python版本,可能要受到一點(diǎn)阻礙,網(wǎng)上有各python版本對(duì)應(yīng)的magic number表,有需要可自行搜索。

在十六進(jìn)制編輯器中打開(kāi)my_app_name的pyc文件。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

然后將上面代碼得到的magic number添加到此文件的最前面(表示版本信息,很重要)。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

然后再補(bǔ)充24個(gè)0(這些東西代表時(shí)間、代碼大小等信息,沒(méi)什么用,可以全部用0填充)

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

最后保存文件。

如果不進(jìn)行這一步,那么下一步反編譯pyc時(shí)將會(huì)報(bào)錯(cuò),提示magic number有誤。

反編譯pyc

將pycdc.exe和my_app_name的pyc文件放到同一文件夾下。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

在當(dāng)前文件夾下啟動(dòng)cmd,輸入:

pycdc my_app_name>final_my_app_name.py

當(dāng)前文件夾下生成了final_my_app_name.py,這就是反編譯的結(jié)果。事實(shí)上,結(jié)果并不完美,存在很多錯(cuò)誤,需要后期進(jìn)行調(diào)整。

?pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

除了pycdc,常用于反編譯pyc文件的還有uncompyle6,但是目前(截至2023.12.17)不支持python3.9以上的版本。還有一個(gè)在線反編譯pyc工具(有限制):python反編譯 - 在線工具

反編譯依賴(lài)庫(kù)

以上的方法用于反編譯主文件exe,如果想要反編譯這個(gè)應(yīng)用依賴(lài)的python模塊,可以進(jìn)入xxxx.exe_extracted文件夾下的PYZ-00.pyz_extracted,里面包含了這個(gè)應(yīng)用所需的依賴(lài)模塊的pyc文件。按照上一節(jié)的方法即可進(jìn)行反編譯。

7.2 編譯為pyd文件以防止反編譯

在打包前將一些依賴(lài)的*.py文件編譯成*.pyd文件,可以大大增加反編譯的難度。*.pyd是動(dòng)態(tài)鏈接庫(kù),它可以像python模塊一樣調(diào)用但不能直接運(yùn)行。使用*.pyd不僅可以增加反編譯難度,還能提升代碼速度。

作者根據(jù)python源碼打包成exe、exe反編譯、pyd加密防止反編譯_unknown magic number 227 in-CSDN博客

的方法進(jìn)行了嘗試但是效果并不好,以下僅給出方法。

調(diào)整代碼

在開(kāi)始之前,我們需要先對(duì)代碼進(jìn)行調(diào)整。*.pyd文件只能被導(dǎo)入但不能直接運(yùn)行,所以主程序不能進(jìn)行pyd編譯。所以,這樣做以后依賴(lài)文件不會(huì)被反編譯,但主程序還是會(huì)被反編譯。我們可以進(jìn)行一些改變,在主文件中留下那些不重要的代碼,讓反編譯者看不到什么寶貴的信息,將重要的程序內(nèi)容放到一個(gè)模塊中,在主文件中只進(jìn)行調(diào)用。

在my_app_name.py文件夾下新建一個(gè)*.py文件(命名是隨意的,這里把它命名為module.py),在這個(gè)module.py文件中寫(xiě)入原本是my_app_name.py中的內(nèi)容,不過(guò)做了一些改變,加了一個(gè)main函數(shù)用于運(yùn)行。

import tkinter as tk # 導(dǎo)入tkinter

def main():
    root = tk.Tk() # 創(chuàng)建窗口
    root.title("我的應(yīng)用程序") # 更改標(biāo)題

    image = tk.PhotoImage(file="assets/image.gif")
    label = tk.Label(root, text="你好,用戶(hù)!", image=image, compound="top")
    label.pack() # 顯示圖片

    root.mainloop() # 保持窗口運(yùn)行

再來(lái)修改my_name_app.py。這個(gè)入口程序直接從module.py(編譯后就變成module.pyd了)中導(dǎo)入main函數(shù),然后運(yùn)行。

'''
一個(gè)簡(jiǎn)單的應(yīng)用
'''

from module import * # 這里不能寫(xiě)成from module import main,原因見(jiàn)8.1

if __name__ == "__main__":
    main()

這樣一來(lái),反編譯后也只能看到幾行與main相關(guān)的內(nèi)容,根本無(wú)法獲取有用的代碼信息。?

import tkinter
from module import main

if __name__ == "__main__":
    main()

下載工具?

先用pip下載Cython:

pip install Cython

此外還需要配置Visual Studio的C++開(kāi)發(fā)工具。

先在下方鏈接下載Visual Studio:

Microsoft C++ 生成工具 - Visual Studio

安裝生成工具時(shí),勾選“使用C++的桌面開(kāi)發(fā)”并點(diǎn)擊右下方安裝。如果你已經(jīng)安裝了生成工具但沒(méi)有勾選這一項(xiàng),可以啟動(dòng)安裝包后,點(diǎn)擊“修改”按鈕進(jìn)行更改。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

然后等待安裝完成。

編譯成pyd

在my_app_name.py的文件夾下新建setup.py,并輸入以下代碼:

from distutils.core import setup
from Cython.Build import cythonize
 
setup(ext_modules=cythonize(["module.py", ]))

用ext_modules參數(shù)指定需要進(jìn)行編譯的文件。如果還有別的python文件需要編譯,可以在列表中修改。

保存文件后,啟動(dòng)cmd,運(yùn)行以下命令:

python setup.py build_ext --inplace

完成編譯后,文件夾中會(huì)生成一些新的文件,從中找出與模塊名同名的*.pyd文件,其他沒(méi)用的文件可以刪掉。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

這個(gè)文件名中有一些諸如cp310-win...的東西,表示這個(gè)python版本、系統(tǒng)等信息,需要手動(dòng)將它們刪除,只保留模塊名和后綴名。

pyinstaller打包exe,python,開(kāi)發(fā)語(yǔ)言

?生成pyd文件后,無(wú)需管原來(lái)的文件module.py,因?yàn)閜ython導(dǎo)入時(shí)會(huì)優(yōu)先選擇pyd后綴的模塊。

如果輸入setup的命令后,出現(xiàn)了一些報(bào)錯(cuò)信息,提示需要Microsoft Visual C++,則代表上一步?jīng)]有正確完成。

進(jìn)行exe打包

完成以上步驟后,可以通過(guò)pyinstaller進(jìn)行打包了。

8 注意事項(xiàng)與常見(jiàn)問(wèn)題

注意事項(xiàng):導(dǎo)入?

作者似乎發(fā)現(xiàn),在導(dǎo)入python模塊時(shí),如果使用from xxx?import func的形式,那么pyinstaller只會(huì)把導(dǎo)入的func包含到打包的應(yīng)用里面,而不是整個(gè)xxx模塊。并且這個(gè)過(guò)程中,pyinstaller不會(huì)管xxx模塊中還依賴(lài)于哪些模塊,這就可能導(dǎo)致func函數(shù)根本無(wú)法運(yùn)行。

比如我有同一目錄下的main.py和module.py:

'''module.py'''
import tkinter

def main():
    tkinter.Tk()
    tkinter.mainloop()
'''main.py'''
from module import main

if __name__ == "__main__":
    main()

如果用pyinstaller對(duì)main.py進(jìn)行編譯,那么最后就會(huì)因?yàn)檎也坏絫kinter模塊而閃退。這是因?yàn)閙ain.py中只導(dǎo)入了module.py中的main函數(shù),pyinstaller會(huì)進(jìn)行優(yōu)化,只打包def main()函數(shù)的這一部分,而不會(huì)打包module.py中的其他內(nèi)容(包括import tkinter這一重要的一句)

直接用python運(yùn)行main.py時(shí)就不會(huì)出現(xiàn)以上問(wèn)題。因?yàn)閜ython解釋from xxx import func這一句時(shí),還是會(huì)先把xxx模塊運(yùn)行一次,但是只保留了一個(gè)func的變量名。

這里提供三種解決方案:

  • 換一種導(dǎo)入方式,使用import xxx或from xxx import *(推薦)
  • 在主程序中把模塊所需的依賴(lài)全導(dǎo)進(jìn)來(lái)(在上面的示例中,就是main.py中添加import tkinter這一句)
  • 打包時(shí)指定--hidden-import等參數(shù)(不推薦)

編譯時(shí)報(bào)錯(cuò):不是可運(yùn)行的命令或程序

首先檢查pyinstaller是否被成功安裝。在cmd輸入pip list,看安裝列表中是否存在pyinstaller,如果沒(méi)有則重新安裝,根據(jù)安裝信息進(jìn)行處理。

如果顯示未找到pyinstaller,則應(yīng)用絕對(duì)路徑指定pyinstaller。首先進(jìn)入所在的文件夾,然后復(fù)制路徑。打包時(shí),將pyinstaller替換為pyinstaller.exe的絕對(duì)路徑。(pyi-makespec找不到同理)

  • Windows: Python目錄下的Scripts文件夾

  • GNU/Linux: /usr/bin

  • macOS (using the default Apple-supplied Python)?/usr/bin

  • macOS (using Python installed by homebrew)?/usr/local/bin

  • macOS (using Python installed by macports)?/opt/local/bin

例如,你想要執(zhí)行pyinstaller myscript.py,但是提示找不到pyinstaller.exe。在你的電腦上,pyinstaller.exe安裝在了C:\Python\Python310\Scripts這個(gè)位置,那么執(zhí)行:

C:\Python\Python310\Scripts\pyinstaller.exe myscript.py

還有一種解決方法,將pyinstaller.exe所在的文件夾添加到系統(tǒng)環(huán)境變量(推薦)。Windows上添加環(huán)境變量辦法如下:

右擊“此電腦” -> 單擊“屬性” -> 單擊“高級(jí)系統(tǒng)設(shè)置” -> 單擊“環(huán)境變量” -> 在用戶(hù)變量的位置單擊“Path” -> 單擊“編輯” -> 在“編輯環(huán)境變量”的窗口單擊“新建” -> 寫(xiě)入pyinstaller.exe的所在路徑 -> 一路“確定”進(jìn)行保存。

運(yùn)行后報(bào)錯(cuò):找不到某些模塊或文件

找不到某些模塊,需要修改命令行參數(shù)--hidden-import,加入找不到的模塊。

如果提示找不到sys, os兩個(gè)模塊,那么命令行參數(shù)修改為:

pyinstaller --hidden-import "sys" --hidden-import "os" ...

如果使用Spec打包,則應(yīng)在Analysis類(lèi)的hiddenimport參數(shù)的列表中添加找不到的模塊。

如果只是找不到某些模塊中的部分文件,則需要為該模塊添加鉤子,或者將這些文件傳遞到命令行參數(shù)--add-data, --add-binary中。

運(yùn)行后報(bào)錯(cuò):失去標(biāo)準(zhǔn)輸入

RuntimeError: input(): lost sys.stdin

某些程序打包后出現(xiàn)以上報(bào)錯(cuò)內(nèi)容。這是由于代碼中使用了input這樣的函數(shù)讓用戶(hù)進(jìn)行輸入,但是打包時(shí)卻設(shè)置了隱藏控制臺(tái)。于是,運(yùn)行打包后的應(yīng)用后就沒(méi)有一個(gè)控制臺(tái)讓用戶(hù)進(jìn)行輸入,就會(huì)報(bào)錯(cuò)。(失去stdout并不會(huì)報(bào)錯(cuò),但是print內(nèi)容不會(huì)顯示)

運(yùn)行時(shí)閃退

閃退是由于程序中出現(xiàn)了致命的異常,可能由多種原因?qū)е?,需要根?jù)引發(fā)的異常進(jìn)行處理。這里提供一種方法來(lái)看到造成閃退的報(bào)錯(cuò)信息。

在當(dāng)前文件夾下啟動(dòng)cmd(方法:將文件資源管理器窗口上方的文件路徑修改為"cmd",然后回車(chē)),然后運(yùn)行:

my_app_name.exe

此方法通過(guò)命令行運(yùn)行這個(gè)應(yīng)用。如果程序閃退,命令行窗口不會(huì)關(guān)閉,上面就留下了報(bào)錯(cuò)信息。

由于文檔內(nèi)容較多,很難涵蓋每一個(gè)點(diǎn),如果你認(rèn)為還有本文沒(méi)有提及的重點(diǎn),請(qǐng)指出,歡迎任何建議者。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-718242.html

到了這里,關(guān)于Python pyinstaller打包exe最完整教程的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 通過(guò)pyinstaller將python項(xiàng)目打包成exe執(zhí)行文件

    目錄 第一步:安裝pyinstaller 第二步:獲取一個(gè)ico圖標(biāo)(也即是自己這個(gè)exe文件最后的圖標(biāo)) 第三步:打包 pip install pyinstaller 先從這里獲取一個(gè)png圖片:iconfont-阿里巴巴矢量圖標(biāo)庫(kù) 再轉(zhuǎn)化成ico圖片(推薦256 x 256):PNG轉(zhuǎn)ICO - 在線轉(zhuǎn)換圖標(biāo)文件 ?可以把得到的ico圖片放在pytho

    2024年02月09日
    瀏覽(32)
  • python項(xiàng)目打包成exe可執(zhí)行文件(pyinstaller庫(kù))

    python項(xiàng)目打包成exe可執(zhí)行文件(pyinstaller庫(kù))

    前言:才入職新公司;進(jìn)來(lái)工作內(nèi)容,就是將之前的python腳本項(xiàng)目打包成.exe可執(zhí)行文件;一來(lái)方便不同崗位的同事之間使用,二來(lái)源碼信息不會(huì)改動(dòng)和泄漏。 正因?yàn)閜ython具有豐富的第三方庫(kù),目前已有數(shù)萬(wàn)之多;這里用到的是 \\\'pyinstaller\\\'庫(kù); 正文: 兩種情況: Ⅰ、單獨(dú)一個(gè)

    2024年02月06日
    瀏覽(24)
  • python pyinstaller打包常見(jiàn)問(wèn)題(一):無(wú)法生成exe文件/打包閃退

    python pyinstaller打包常見(jiàn)問(wèn)題(一):無(wú)法生成exe文件/打包閃退

    小游戲程序,本地環(huán)境能正常運(yùn)行 程序調(diào)用了wav文件,即音效資源文件 程序調(diào)用wav路徑正確,也是采用絕對(duì)路徑 程序制作完成后,進(jìn)行了以下三步打包操作: 在需要打包的程序的目錄上,我進(jìn)入終端輸入以下命令進(jìn)行打包: 2.1、打開(kāi)生成的snake.spec文件 2.2、修改Analysis 的

    2024年01月22日
    瀏覽(94)
  • python pyinstaller spec文件 打包多個(gè)python文件為exe應(yīng)用程序

    python pyinstaller spec文件 打包多個(gè)python文件為exe應(yīng)用程序

    使用pyinstaller 單個(gè)文件打包 使用pyinstaller 多個(gè)文件打包 這里先看下項(xiàng)目文件結(jié)構(gòu): 編輯PaserAdapter_Test.spec文件 執(zhí)行打包命令(可指定輸出程序存放的路徑) 假設(shè)使用默認(rèn)路徑 指定程序存放路徑 如果在windows系統(tǒng)打包,則dist下會(huì)有對(duì)應(yīng)的.exe程序,此時(shí)可脫離其他源文件獨(dú)自

    2024年02月16日
    瀏覽(32)
  • 【Python】conda虛擬環(huán)境下使用pyinstaller打包程序?yàn)閑xe

    【Python】conda虛擬環(huán)境下使用pyinstaller打包程序?yàn)閑xe

    第一點(diǎn)是,pyinstaller打包需要指定程序中使用的庫(kù)的路徑,新手在這一點(diǎn)上很容易出現(xiàn)問(wèn)題,導(dǎo)致打包后的exe程序執(zhí)行的時(shí)候顯示 缺少模塊 。這個(gè)問(wèn)題可能并不能通過(guò)簡(jiǎn)單地指定路徑來(lái)解決。 第二點(diǎn)就是使用虛擬環(huán)境本身的好處了: 可以創(chuàng)建多個(gè)不同的Python環(huán)境,每個(gè)環(huán)境

    2024年02月09日
    瀏覽(36)
  • 含有openpyxl庫(kù)pyinstaller 打包exe出錯(cuò)python解決方案

    在代碼開(kāi)頭添加以下代碼在打包。 如遇到錯(cuò)誤, win32ctypes.pywin32.pywintypes.error: (110, ‘EndUpdateResource’, ‘系統(tǒng)無(wú)法打開(kāi)指定的設(shè)備或文件?!? ,即可能是openpyxl庫(kù)引發(fā)的

    2024年01月25日
    瀏覽(28)
  • 【python】之pyinstaller模塊,python程序打包成一個(gè)可執(zhí)行exe 文件(超詳細(xì))!

    【python】之pyinstaller模塊,python程序打包成一個(gè)可執(zhí)行exe 文件(超詳細(xì))!

    PyInstaller是一個(gè)在Windows、GNU/Linux、macOS等平臺(tái)下將Python程序凍結(jié)(打包)為獨(dú)立可執(zhí)行文件的工具, 用于在未安裝Python的平臺(tái)上執(zhí)行Python編寫(xiě)的應(yīng)用程序 使用pip接口進(jìn)行安裝 1.打開(kāi)命令行: 切換到要轉(zhuǎn)換的python文件路徑,輸入上述指令。 (1)-F: 只生成一個(gè)單個(gè)文件(只有一個(gè)

    2024年02月12日
    瀏覽(22)
  • Python封裝——如何把python代碼打包成一個(gè)可執(zhí)行的.exe文件,Pyinstaller

    Python封裝——如何把python代碼打包成一個(gè)可執(zhí)行的.exe文件,Pyinstaller

    https://pyinstaller.org/en/stable/ Pyinstaller可以打包Win、Linux、MacOS下的python程序,在什么環(huán)境下打包的就是什么平臺(tái)的程序。相對(duì)來(lái)看,Pyinstaller打包的程序體積大、安全性稍差(容易反編譯),但是上手相對(duì)簡(jiǎn)單一些。 安裝:cmd下輸入pip install Pyinstaller(P大寫(xiě)) 清華大學(xué)鏡像 pip in

    2024年02月10日
    瀏覽(33)
  • 使用pyinstaller將具有多個(gè)python文件的項(xiàng)目打包為exe(含依賴(lài)庫(kù))

    使用pyinstaller將具有多個(gè)python文件的項(xiàng)目打包為exe(含依賴(lài)庫(kù))

    1、將需要打包有python文件放到一個(gè)文件夾,例如下圖所示的Demo文件夾,其中ClickEveryDay.py為主文件,telegram.ico為圖標(biāo)文件。 ??2、生成主函數(shù)對(duì)應(yīng)的spec文件 命令:在Demo文件夾路徑下使用:pyi-makespec ClickEveryDay.py,將產(chǎn)生一個(gè)ClickEveryDay.spec文件。 3、修改ClickEveryDay.spec文件:加

    2024年02月11日
    瀏覽(26)
  • Python使用pyinstaller打包成.exe文件執(zhí)行后閃退的解決辦法

    Python使用pyinstaller打包成.exe文件執(zhí)行后閃退的解決辦法

    1、程序在PyChram中運(yùn)行正常,但是用pyinstaller將程序打包成.exe文件執(zhí)行后閃退; 2、使用cmd在在.exe文件處./執(zhí)行后,發(fā)現(xiàn)提示 No module named ‘ttkbootstrap’ ,沒(méi)有ttkbootstrap模塊; 3、在工程文件的venvLibsite-packages下,確實(shí)存在ttkbootstrap包;這個(gè)包不知什么原因沒(méi)打包進(jìn).exe文件,

    2024年02月12日
    瀏覽(110)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包