python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python文件打包为exe

Python实现将文件打包为exe的完整指南

作者:阿幸软件杂货间

这篇文章主要为大家详细介绍了如何使用Python实现将文件打包为exe,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

软件介绍

因为我最近一直在用Python制作程序,每次都在cmd界面进行打包,每次都要输出py文件路径,感觉效率比较低,所以就做了这么一个小工具。

软件功能

Python文件路径

这里是输入你要打包的py文件,路径是可以有中文的。

ico路径

这个是你打包的exe程序的图标,默认没有,当然你也可以直接输入其路径。

输出程序路径

这个就是你打包好的可执行程序路径,默认路径是:C:\Users\Administrator\dist

不过我这个增加了可以自定义输出路径,也是支持中文路径的,然后打包完成之后会弹窗提醒你是否打开文件路径。

目前不支持输出程序路径名字自定义,后面有时间再加进去吧。

是否运行程序时出现cmd界面

这个一般是建议不用出现

要不然会额外多一个cmd界面,看着也不舒服,没必要。

源码提供

下面是我提供的源码,你们可以直接用,然后就是我文末也提供了打包好了的成品,有需要也可以获取。

import sys
import os
import subprocess
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit, 
                            QPushButton, QVBoxLayout, QHBoxLayout, QWidget,
                            QFileDialog, QTextEdit, QMessageBox, QProgressBar,
                            QCheckBox)  # 新增QCheckBox导入
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QIcon

class PackagerThread(QThread):
    """打包进程的线程类,避免UI卡顿"""
    log_signal = pyqtSignal(str)
    progress_signal = pyqtSignal(int)
    finished_signal = pyqtSignal(bool, str)
    
    def __init__(self, python_file, icon_file=None, no_console=False, output_dir=None):
        super().__init__()
        self.python_file = python_file
        self.icon_file = icon_file
        self.no_console = no_console  # 是否不显示CMD界面
        self.output_dir = output_dir  # 自定义输出目录
    
    def run(self):
        try:
            # 检查文件是否存在
            if not os.path.exists(self.python_file):
                self.log_signal.emit(f"错误:文件 {self.python_file} 不存在")
                self.finished_signal.emit(False, "文件不存在")
                return
                
            # 检查是否安装了pyinstaller
            try:
                subprocess.check_output(["pyinstaller", "--version"], 
                                       stderr=subprocess.STDOUT, 
                                       text=True)
            except (subprocess.CalledProcessError, FileNotFoundError):
                self.log_signal.emit("错误:未安装pyinstaller,请先安装:pip install pyinstaller")
                self.finished_signal.emit(False, "未安装pyinstaller")
                return
            
            # 构建pyinstaller命令
            cmd = ["pyinstaller", "--onefile", "--name", os.path.splitext(os.path.basename(self.python_file))[0]]
            
            # 添加无控制台参数(-w)
            if self.no_console:
                cmd.append("--windowed")  # 等价于-w,不显示控制台
                self.log_signal.emit("已启用:运行时不显示CMD界面")
            
            # 添加自定义输出目录(--distpath)
            if self.output_dir and os.path.isdir(self.output_dir):
                cmd.extend(["--distpath", self.output_dir])
                self.log_signal.emit(f"自定义输出目录:{self.output_dir}")
            else:
                self.log_signal.emit("使用默认输出目录(当前目录下的dist文件夹)")
            
            # 如果提供了图标文件,则添加图标参数
            if self.icon_file and os.path.exists(self.icon_file):
                cmd.extend(["--icon", self.icon_file])
                self.log_signal.emit(f"使用图标文件:{self.icon_file}")
            
            # 添加要打包的Python文件
            cmd.append(self.python_file)
            
            self.log_signal.emit(f"开始打包,命令:{' '.join(cmd)}")
            self.progress_signal.emit(20)
            
            # 执行打包命令
            process = subprocess.Popen(cmd, stdout=subprocess.PIPE, 
                                      stderr=subprocess.STDOUT, 
                                      text=True, 
                                      bufsize=1)
            
            # 实时输出日志
            while True:
                output = process.stdout.readline()
                if output == '' and process.poll() is not None:
                    break
                if output:
                    self.log_signal.emit(output.strip())
            
            self.progress_signal.emit(80)
            
            # 检查执行结果
            exit_code = process.poll()
            if exit_code == 0:
                # 确定最终输出目录(优先使用自定义目录)
                dist_dir = self.output_dir if (self.output_dir and os.path.isdir(self.output_dir)) else os.path.join(os.getcwd(), "dist")
                exe_path = os.path.join(dist_dir, os.path.splitext(os.path.basename(self.python_file))[0] + ".exe")
                self.log_signal.emit(f"打包成功!可执行文件位于:{exe_path}")
                self.progress_signal.emit(100)
                self.finished_signal.emit(True, exe_path)
            else:
                self.log_signal.emit(f"打包失败,返回代码:{exit_code}")
                self.finished_signal.emit(False, f"打包失败,返回代码:{exit_code}")
                
        except Exception as e:
            self.log_signal.emit(f"发生错误:{str(e)}")
            self.finished_signal.emit(False, str(e))

class PyToExeConverter(QMainWindow):
    """主窗口类"""
    def __init__(self):
        super().__init__()
        self.init_ui()
        
    def init_ui(self):
        # 设置窗口标题和大小
        self.setWindowTitle("Python转EXE工具@阿幸")
        self.setGeometry(100, 100, 800, 650)  # 适当增加高度容纳新控件
        
        # 创建中心部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 主布局
        main_layout = QVBoxLayout(central_widget)
        main_layout.setSpacing(10)
        main_layout.setContentsMargins(20, 20, 20, 20)
        
        # Python文件路径选择
        python_file_layout = QHBoxLayout()
        self.python_file_label = QLabel("Python文件路径:")
        self.python_file_edit = QLineEdit()
        self.python_file_btn = QPushButton("浏览...")
        self.python_file_btn.clicked.connect(self.select_python_file)
        
        python_file_layout.addWidget(self.python_file_label)
        python_file_layout.addWidget(self.python_file_edit)
        python_file_layout.addWidget(self.python_file_btn)
        
        # 图标文件路径选择
        icon_file_layout = QHBoxLayout()
        self.icon_file_label = QLabel("图标文件路径(可选):")
        self.icon_file_edit = QLineEdit()
        self.icon_file_btn = QPushButton("浏览...")
        self.icon_file_btn.clicked.connect(self.select_icon_file)
        
        icon_file_layout.addWidget(self.icon_file_label)
        icon_file_layout.addWidget(self.icon_file_edit)
        icon_file_layout.addWidget(self.icon_file_btn)
        
        # 输出目录选择(新增)
        output_dir_layout = QHBoxLayout()
        self.output_dir_label = QLabel("输出目录(可选):")
        self.output_dir_edit = QLineEdit()
        self.output_dir_btn = QPushButton("浏览...")
        self.output_dir_btn.clicked.connect(self.select_output_dir)
        
        output_dir_layout.addWidget(self.output_dir_label)
        output_dir_layout.addWidget(self.output_dir_edit)
        output_dir_layout.addWidget(self.output_dir_btn)
        
        # 无控制台选项(新增)
        self.no_console_check = QCheckBox("运行EXE时不显示CMD界面")
        self.no_console_check.setChecked(True)  # 默认勾选
        
        # 打包按钮
        self.pack_btn = QPushButton("开始打包")
        self.pack_btn.clicked.connect(self.start_packaging)
        self.pack_btn.setStyleSheet("font-size: 14px; padding: 8px;")
        
        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        
        # 日志输出区域
        self.log_label = QLabel("打包日志:")
        self.log_edit = QTextEdit()
        self.log_edit.setReadOnly(True)
        self.log_edit.setStyleSheet("background-color: #f0f0f0;")
        
        # 添加所有部件到主布局(新增了输出目录和复选框)
        main_layout.addLayout(python_file_layout)
        main_layout.addLayout(icon_file_layout)
        main_layout.addLayout(output_dir_layout)
        main_layout.addWidget(self.no_console_check)
        main_layout.addWidget(self.pack_btn, alignment=Qt.AlignCenter)
        main_layout.addWidget(self.progress_bar)
        main_layout.addWidget(self.log_label)
        main_layout.addWidget(self.log_edit)
        
        # 状态提示
        self.statusBar().showMessage("就绪")
    
    # 新增:选择输出目录
    def select_output_dir(self):
        dir_path = QFileDialog.getExistingDirectory(
            self, "选择输出目录", os.getcwd()
        )
        if dir_path:
            self.output_dir_edit.setText(dir_path)
        
    def select_python_file(self):
        """选择Python文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择Python文件", "", "Python Files (*.py);;All Files (*)"
        )
        if file_path:
            self.python_file_edit.setText(file_path)
    
    def select_icon_file(self):
        """选择图标文件"""
        file_path, _ = QFileDialog.getOpenFileName(
            self, "选择图标文件", "", "Icon Files (*.ico);;All Files (*)"
        )
        if file_path:
            self.icon_file_edit.setText(file_path)
    
    def append_log(self, text):
        """添加日志到日志区域"""
        self.log_edit.append(text)
        # 自动滚动到底部
        self.log_edit.verticalScrollBar().setValue(
            self.log_edit.verticalScrollBar().maximum()
        )
    
    def update_progress(self, value):
        """更新进度条"""
        self.progress_bar.setValue(value)
    
    def start_packaging(self):
        """开始打包过程"""
        python_file = self.python_file_edit.text().strip()
        icon_file = self.icon_file_edit.text().strip() if self.icon_file_edit.text().strip() else None
        no_console = self.no_console_check.isChecked()  # 获取复选框状态
        output_dir = self.output_dir_edit.text().strip() if self.output_dir_edit.text().strip() else None  # 获取输出目录
        
        # 验证输入
        if not python_file:
            QMessageBox.warning(self, "输入错误", "请选择要打包的Python文件")
            return
            
        if not python_file.endswith(".py"):
            QMessageBox.warning(self, "文件错误", "请选择扩展名为.py的Python文件")
            return
            
        # 检查图标文件是否存在(如果提供了的话)
        if icon_file and not os.path.exists(icon_file):
            QMessageBox.warning(self, "文件错误", f"图标文件不存在:{icon_file}")
            return
        
        # 检查输出目录是否存在(如果提供了的话)
        if output_dir and not os.path.isdir(output_dir):
            QMessageBox.warning(self, "目录错误", f"输出目录不存在:{output_dir}")
            return
        
        # 准备打包
        self.log_edit.clear()
        self.pack_btn.setEnabled(False)
        self.progress_bar.setVisible(True)
        self.progress_bar.setValue(0)
        self.statusBar().showMessage("正在打包...")
        
        # 创建并启动打包线程(传入新参数)
        self.packager_thread = PackagerThread(
            python_file, 
            icon_file, 
            no_console=no_console, 
            output_dir=output_dir
        )
        self.packager_thread.log_signal.connect(self.append_log)
        self.packager_thread.progress_signal.connect(self.update_progress)
        self.packager_thread.finished_signal.connect(self.on_packaging_finished)
        self.packager_thread.start()
    
    def on_packaging_finished(self, success, message):
        """打包完成后的处理"""
        self.pack_btn.setEnabled(True)
        self.statusBar().showMessage("打包完成" if success else "打包失败")
        
        if success:
            QMessageBox.information(self, "成功", f"打包成功!\n可执行文件位于:\n{message}")
            # 询问是否打开输出目录
            if QMessageBox.question(self, "打开目录", "是否打开输出目录?", 
                                   QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
                output_dir = os.path.dirname(message)
                if os.name == 'nt':  # Windows系统
                    os.startfile(output_dir)
                elif os.name == 'posix':  # Linux或macOS
                    subprocess.run(['open' if sys.platform == 'darwin' else 'xdg-open', output_dir])
        else:
            QMessageBox.critical(self, "失败", f"打包失败:\n{message}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = PyToExeConverter()
    window.show()
    sys.exit(app.exec_())

到此这篇关于Python实现将文件打包为exe的完整指南的文章就介绍到这了,更多相关Python文件打包为exe内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文