實用指南:【Python】基于 PyQt6 和 Conda 的 PyInstaller 打包工具
文章目錄
一、概述(Readme)
適用場景: 使用conda虛擬環境
項目地址:EasyPack
App 截圖:
pyinstaller -F --paths=E:\anaconda3\envs\yt_dlp_env\Lib\site-packages --python=E:\anaconda3\envs\yt_dlp_env\python.exe --noconsole --icon=wx2.ico --name=Downloader test.py
這是一句常用的 pyinstaller打包的命令,但每次都手動輸入這一長串命令,很是麻煩。
本項目最初是使用 Tkinter 實現的一個簡單的打包程序。
現在,它已經演進為一個功能較為豐富、界面美觀的 PyQt6 應用,旨在解決直接使用 pyinstaller 命令行時參數復雜、配置繁瑣的問題。
此工具特別為使用 Conda 進行環境管理的 Python 開發者設計,能夠自動檢測并列出您的 Conda 虛擬環境,極大地簡化了路徑配置過程。您只需通過幾次點擊,即可完成對 Python 腳本的打包,無需記憶復雜的命令。
主要功能
- 美觀的圖形用戶界面:采用 PyQt6 構建,提供清爽的左右分欄布局,配置區和日志區一目了然。
- 主題切換:內置亮色和暗色兩套主題,可根據個人喜好一鍵切換。
- 深度 Conda 集成:
- 自動掃描并列出所有本地 Conda 虛擬環境。
- 選擇環境后,自動填充該環境的 Python 解釋器路徑和
site-packages路徑。
- 智能依賴檢查:在構建開始前,自動檢查所選環境中是否安裝了
pyinstaller,如果未安裝,會提示用戶一鍵安裝。 - 全面的打包選項:
- 基本選項:支持單文件 (
-F) 和單目錄 (-D) 模式切換、自定義程序名稱 (--name)、添加圖標 (--icon)、隱藏控制臺 (--noconsole) 等常用功能。 - 高級選項:支持通過圖形界面添加附加數據文件/目錄 (
--add-data)、添加隱藏的模塊導入 (--hidden-import) 等。
- 基本選項:支持單文件 (
- 實時日志輸出:在獨立的線程中執行打包命令,構建過程的日志會實時顯示在右側的日志窗口中,界面不會卡頓。
- 統一的輸出管理:所有 PyInstaller 生成的文件(
build目錄、dist目錄、.spec文件)都會被統一整理到源腳本同級目錄下的output文件夾中,保持項目根目錄的整潔。 - 便捷操作:打包成功后,“打開輸出目錄”按鈕會被激活,可以一鍵直達生成的可執行文件所在的位置。
1.1 環境要求
在運行此程序之前,請確保您的系統滿足以下條件:
- Python: 3.8 或更高版本。
- PyQt6:
pip install PyQt6 - Conda: 已安裝 Anaconda 或 Miniconda,并且
conda命令已添加到系統的環境變量 (PATH) 中,可以在命令行中直接調用。
1.2 如何使用
直接下載已經打包好的exe文件即可,或者克隆本倉庫。
打包步驟:
- 選擇腳本:點擊 “Python 腳本” 右側的 “瀏覽…” 按鈕,選擇您想要打包的主 Python 文件。程序名稱通常會自動根據文件名填充。
- 選擇環境:在 “Conda 環境” 下拉菜單中,選擇您的項目所使用的虛擬環境。選擇后,相關的模塊路徑會自動填充。
- 自定義選項:
- 根據需要修改程序名稱或添加圖標。
- 選擇打包模式(單文件或單目錄)。
- 在 “高級打包選項” 中添加額外的數據文件或需要強制包含的庫。
- 預覽命令:在右側的 “最終命令預覽” 窗口中檢查即將執行的命令是否符合預期。
- 開始構建:點擊右下角的 “開始構建” 按鈕。
- 查看日志:在 “構建日志” 窗口中觀察實時輸出。
- 完成:
- 如果構建成功,日志中會以綠色字體顯示成功信息,并且 “打開目錄” 按鈕會變為可用狀態。
- 如果構建失敗,日志中會以紅色字體顯示失敗信息,您可以根據日志排查問題。
二、詳解
2.1 架構設計:解耦視圖、邏輯與控制
應用分解為三個獨立的 Python 模塊。
ui_components.py- 視圖層 (View)- 技術棧: PyQt6
- 職責: 完全負責 UI 的構建、布局和樣式。它包含一個
PyInstallerGUI類,該類繼承自QMainWindow,并定義了所有的窗口控件(QWidget)、布局管理器(QLayout)和樣式表(QSS)。此模塊不包含任何業務邏輯,只通過信號(Signal)對外暴露用戶交互事件。
builder.py- 邏輯/工作層 (Worker)- 技術棧:
QObject,QThread,subprocess - 職責: 處理所有耗時和后臺任務。核心是
BuildWorker類,它繼承自QObject,其run方法內封裝了執行 PyInstaller 命令的邏輯。此類被設計為在獨立的QThread中運行,以防止 UI 主線程阻塞。它通過信號向控制層報告進度和結果。
- 技術棧:
main.py- 主控層 (Controller)- 技術棧:
QApplication - 職責: 程序的入口和“膠水層”。它負責實例化
QApplication和視圖PyInstallerGUI,創建并管理QThread和BuildWorker。其核心任務是連接視圖層發出的用戶操作信號和邏輯層執行任務的槽(Slot),并處理邏輯層返回的信號。
- 技術棧:
2.2 核心技術實現細節
2.21 異步執行與 UI 響應:QThread 與信號/槽機制
GUI 應用的主線程負責渲染界面和響應用戶輸入。任何耗時操作(如文件 I/O、網絡請求、子進程執行)如果在此線程中進行,都會導致界面凍結。PyInstaller 的打包過程可能持續數分鐘,因此必須異步執行。
實現步驟:
創建
BuildWorker:BuildWorker類繼承QObject,使其能夠利用 Qt 的元對象系統,特別是信號/槽機制。打包命令的執行邏輯被封裝在run方法中。# builder.py class BuildWorker(QObject): progress_updated = pyqtSignal(str) # 定義信號,用于發送日志 finished = pyqtSignal(int) # 定義信號,用于報告完成狀態 def __init__(self, command, python_executable): super().__init__() self.command = command # ... def run(self): # 使用 subprocess.Popen 啟動 PyInstaller 進程 process = subprocess.Popen(...) # 逐行讀取子進程的輸出 for line in iter(process.stdout.readline, ''): self.progress_updated.emit(line) # 發射信號,將日志發送出去 process.wait() self.finished.emit(process.returncode) # 發射完成信號線程管理與通信: 在
main.py中,當用戶觸發構建時:- 實例化
QThread和BuildWorker。 - 調用
worker.moveToThread(thread),此操作將worker對象的所有事件處理(包括其槽函數的執行)都轉移到了thread所代表的線程中。 - 連接信號與槽。將
worker的信號連接到view中更新 UI 的方法上。 - 啟動線程。調用
thread.start(),這會觸發thread的started信號,我們將其連接到worker.run方法,從而在新線程中開始執行打包任務。
# main.py def start_build(self): # ... self.build_thread = QThread() self.build_worker = BuildWorker(command, python_exe) self.build_worker.moveToThread(self.build_thread) # 連接 worker 的信號到 view 的槽 self.build_worker.progress_updated.connect(self.view.log_to_console) self.build_worker.finished.connect(self.on_build_finished) # 當線程啟動時,執行 worker 的 run 方法 self.build_thread.started.connect(self.build_worker.run) # 當 worker 完成后,安全退出線程 self.build_worker.finished.connect(self.build_thread.quit) self.build_thread.finished.connect(self.build_thread.deleteLater) self.build_thread.start()- 實例化
通過這種方式,打包任務在獨立的線程中運行,并通過信號機制將狀態安全地傳遞給主線程以更新 UI,保證了界面的流暢性。
2.22 Conda 環境集成:subprocess 與正則表達式
為了實現 Conda 環境的自動發現,程序需要執行 conda env list 命令并解析其輸出。
實現細節:
- 使用
subprocess.run函數以阻塞方式執行conda env list。這是一個輕量級操作,可以安全地在主線程中執行(僅在程序啟動時調用一次)。 - 設置
capture_output=True和text=True來捕獲命令的文本輸出。 - 使用
creationflags=subprocess.CREATE_NO_WINDOW(僅 Windows) 來防止執行conda命令時彈出黑色的控制臺窗口。 - 通過正則表達式
re.compile(r"^(\S+)\s+\*?\s+(.+)$")匹配輸出中的每一行,提取環境名稱和絕對路徑,并存入字典中。
# builder.py -> get_conda_envs()
proc = subprocess.run(
["conda", "env", "list"],
capture_output=True, text=True, check=True, encoding='utf-8',
creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
)
當用戶在 UI 下拉框中選擇一個環境后,程序會根據這個字典找到對應的環境路徑,并自動拼接出該環境下 python.exe 和 Lib/site-packages 的路徑,填充到相應的輸入框中。
2.23 資源文件打包的通用解決方案
在 GUI 應用中,圖標、配置文件等資源在開發時和打包后的路徑是不同的。PyInstaller 打包后會將 --add-data 指定的文件解壓到一個臨時的 _MEIPASS 目錄。必須編寫一個與環境無關的路徑解析函數。
實現:
# ui_components.py
def resource_path(relative_path):
""" 獲取資源的絕對路徑,兼容開發和打包環境 """
try:
# PyInstaller 打包后會創建一個臨時目錄,并將其路徑存入 sys._MEIPASS
base_path = sys._MEIPASS
except AttributeError:
# 在開發環境中,使用常規的相對路徑
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
在代碼中所有需要加載本地資源的地方(如此工具自身的窗口圖標),都通過 resource_path() 函數來獲取路徑。
# 加載窗口圖標
app_icon_path = resource_path("app_icon.ico")
self.setWindowIcon(QIcon(app_icon_path))
同時,在打包此工具自身時,必須在 pyinstaller 命令中明確指出要包含此資源文件:
pyinstaller --add-data "app_icon.ico;." main.py
--add-data 的格式為 源路徑;目標目錄(Windows 用 ;,Linux/macOS 用 :),. 表示打包后的根目錄。
2.24 動態命令生成與輸出路徑管理
為了提供靈活的打包選項并保持項目目錄整潔,PyInstaller 命令是在用戶交互時動態構建的。
實現:
在 ui_components.py 中,get_pyinstaller_command 方法負責從各個 UI 控件(如 QLineEdit, QCheckBox)中獲取當前值,并逐步拼接成一個完整的命令行字符串。
一個關鍵的改進是輸出路徑的統一管理。通過 PyInstaller 的 --distpath, --workpath, 和 --specpath 參數,可以將所有生成物重定向到一個統一的 output 目錄下。
# ui_components.py -> get_pyinstaller_command()
# 基于主腳本路徑,定義所有輸出路徑
script = self.script_path_edit.text()
base_output_dir = os.path.join(os.path.dirname(script), "output")
dist_path = os.path.join(base_output_dir, "dist")
build_path = os.path.join(base_output_dir, "build")
spec_path = base_output_dir
# 將路徑參數添加到命令列表中
command.extend([
f'--distpath="{dist_path}"',
f'--workpath="{build_path}"',
f'--specpath="{spec_path}"'
])
這不僅使用戶的項目目錄保持干凈,也使得“打開目錄”功能可以精確定位到 output/dist 文件夾。
2.25 UI/UX 優化:主題切換與狀態管理
為了提升用戶體驗,實現了一些 UI 層的優化。
主題切換: 定義了兩套 QSS (Qt Style Sheets) 字符串:
LIGHT_THEME和DARK_THEME。通過連接“黑暗模式”復選框的toggled信號到一個槽函數apply_theme,可以調用self.setStyleSheet()方法,實現整個應用主題的一鍵切換。狀態管理: 在構建開始時,必須禁用所有配置相關的控件,以防止用戶在打包過程中修改參數。這通過一個
set_build_state(is_building)方法實現,該方法會遍歷所有輸入控件并設置其setEnabled(not is_building)狀態。同時,“開始構建”和“取消構建”按鈕的可用性也會相應地切換。
三、改進與完善
- 自定義打包命令:pyinstaller還有其它選項,可以加一個自定義命令部分或者添加更多GUI選項
- 附加數據的bug:先添加附加資源(如果需要)后設置其它的選項是ok的,否則會崩潰(
已修正) - 加一個軟件教程頁面,說明各種選項具體作用和注意點等
- 語言切換
有空了寫吧。
浙公網安備 33010602011771號