在本教程第21章【Python程序打包】的第1节教程中,已经详细介绍了如何通过Python的PyInstaller模块将Python程序打包成exe文件。在Windows系统中,exe文件是最常见的可执行文件,直接双击即可打开程序。目前,Windows中大部分软件包括聊天软件、游戏软件、办公软件等基本上都会通过exe文件进行启动。
随着现代全球化软件的开发,开发者需要解决如何将Python程序移植到不同的操作系统上运行,打包成exe可执行文件是解决跨平台运行问题的直接方法,它使得Python程序可以在没有安装Python解释器的计算机上运行,从而扩大了软件的应用范围。
对于非技术用户来说,打包后的exe文件无需用户安装Python环境即可运行,直接双击即可使用,极大降低了使用门槛。
exe文件具有跨平台兼容性,Python程序打包成exe文件后,可以将程序代码、依赖库及运行所需要的环境整合到单一文件或目录中,支持Windows、Mac、Linux等多平台运行,无需针对不同系统调整环境配置。
PyInstaller是Python中免费且开源的第三方库,主要功能是将Python脚本及其依赖项打包为独立的exe可执行文件,使得Python程序可以在未安装Python解释器的计算机上直接运行,本节教程将介绍如何通过Python的Tkinter库和PyInstaller库利用图形用户界面将Python程序打包成独立的exe可执行文件。
1、Python中基于Tkinter+PyInstaller将程序打包成exe工具介绍
首先,电脑上需要提前安装PyInstaller库,可以通过pip执行命令安装“pip install pyinstaller”。PyInstaller库将Python程序打包成exe时,需要通过配置文件来定制打包过程,配置文件允许开发者指定不同的选项,比如选择单文件模式还是多文件模式,打包后是否显示控制台窗口,打包后是否显示软件ico图标,打包后是否显示软件信息等,我们可以通过Tkinter简单的GUI图形界面一键配置所有的打包选项。
功能说明
单文件模式:如果勾选“单文件打包”,可以将Python程序文件打包进一个单独的exe文件中,单个文件便于用户下载和安装。但是,单文件可能无法处理大量的资源文件,特别是那些动态加载的资源,同时单文件由于路径问题,无法生成动态的资源文件,比如无法生成数据库“db”文件。
多文件模式:如果没有勾选“单文件打包”,则默认以“多文件模式”模式打包。在多文件模式中,PyInstaller会创建一个包含所有必需文件的目录,通常包括一个exe文件、一个资源文件夹以及其他依赖文件。开发者在引用资源文件时,可以直接使用文件名,因为所有文件都在同一个目录中。
版本信息配置:如果勾选“添加版本信息”,且成功设置版本信息后,会在当前脚本工作目录下创建一个名为“version_info.txt”的配置文件。在Windows平台上,为应用程序添加版本信息是一个非常有用的功能,版本信息可以帮助用户和开发者识别应用程序的版本、公司名称、版权信息等,同时也能提升应用程序的专业性和可信度。查看软件的版本信息,可以通过鼠标右键点击exe文件 -> 属性 -> 详细信息:可以看到版本号、公司名称、版权信息等。
配置程序图标:如果想为打包后的exe文件添加图标,可以点击“选择图标”按钮,选择指定的ico图标,如果你没有ico图标文件,可以使用在线图标制作工具将png、jpeg等格式的图片转换为ico格式。
配置控制台窗口:如果勾选“无控制台窗口”,当我们双击打包后的exe文件时,不会显示黑色的控制台窗口。对于GUI图形应用程序,通过勾选这个选项可以避免出现命令行窗口。如果你的应用是基于控制台(例如,使用了input()函数),则不能勾选“无控制台窗口”,运行exe文件时必须显示控制台窗口。
2、Python中基于Tkinter+PyInstaller将程序打包成exe工具界面演示
3、Python中基于Tkinter+PyInstaller将程序打包成exe工具,完整代码如下所示:
动手练一练:
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os, sys, subprocess, threading, time, webbrowser
# 获取当前脚本文件所在的目录
default_directory = os.path.dirname(os.path.abspath(__file__))
# 将当前脚本文件所在的目录设置为工作目录
os.chdir(default_directory)
# 定义版本信息文件
version_file = "version_info.txt"
try:
# 检测是否安装了pyinstaller模块
import PyInstaller
except ImportError:
# 如果没有安装,则提示先安装pyinstaller模块
messagebox.showwarning("警告", "请先安装pyinstaller模块:pip install pyinstaller")
# 退出程序
sys.exit()
# 定义主窗口
class ExePack:
def __init__(self, app):
self.app = app
self.running = False
self.start_time = 0
self.elapsed_time = 0
self.app.title("Python程序利用PyInstaller打包exe工具")
# 创建窗口“帮助”菜单栏
self.menu_bar = tk.Menu(self.app)
self.file_menu = tk.Menu(self.menu_bar, tearoff=0)
self.file_menu.add_command(label="关于程序", command=self.program_about)
self.file_menu.add_command(label="程序讲解", command=self.help_window)
self.file_menu.add_separator()
self.file_menu.add_command(label="退出", command=self.app.quit)
self.menu_bar.add_cascade(label="帮助", menu=self.file_menu)
self.app.config(menu=self.menu_bar)
# 设置窗口居中
window_width = 800
window_height = 660
screen_width = self.app.winfo_screenwidth()
screen_height = self.app.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
self.app.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
self.app.resizable(width=False, height=False)
# 配置根窗口网格布局
self.app.grid_columnconfigure(0, weight=1)
self.app.grid_rowconfigure(0, weight=1)
# 窗口样式配置
self.style = ttk.Style()
self.set_styles()
# 主界面创建
self.create_widgets()
# 定义样式函数
def set_styles(self):
self.style.configure("TButton", padding=6)
self.style.configure("TLabel", padding=3)
self.style.configure("TEntry", padding=3)
self.style.configure("Blue.TLabel", foreground="blue", font=("宋体", 20))
# 定义主界面组件
def create_widgets(self):
main_frame = ttk.Frame(self.app, padding=15)
main_frame.grid(row=0, column=0, sticky="nsew")
main_frame.grid_columnconfigure(1, weight=1)
main_frame.grid_rowconfigure(3, weight=1)
# 定义标题面板
title_label = ttk.Label(main_frame, text="Python程序利用PyInstaller打包exe工具", font=('宋体', 20, 'bold'))
title_label.grid(row=0, column=0, columnspan=3, pady=15)
# Python脚本文件选择
file_label = ttk.Label(main_frame, text="Python脚本路径:")
file_label.grid(row=1, column=0, sticky="w", pady=3)
self.file_entry = ttk.Entry(main_frame)
self.file_entry.grid(row=1, column=1, sticky="ew", padx=5)
file_button = ttk.Button(main_frame, text="浏览", command=self.choose_file)
file_button.grid(row=1, column=2, padx=5)
# 配置打包选项框架
config_frame = ttk.LabelFrame(main_frame, text="打包选项", padding=10)
config_frame.grid(row=2, column=0, columnspan=3,
pady=10, sticky="ew")
config_frame.grid_columnconfigure(1, weight=1)
# 打包选项内容
self.single_var = tk.BooleanVar()
single_set = ttk.Checkbutton(config_frame, text="单文件打包", variable=self.single_var)
single_set.grid(row=0, column=0, sticky="w", padx=5)
# 选择是否显示控制台窗口
self.no_console_var = tk.BooleanVar(value=True)
no_console_set = ttk.Checkbutton(config_frame, text="无控制台窗口", variable=self.no_console_var)
no_console_set.grid(row=0, column=1, sticky="w", padx=5)
# 图标设置
icon_label = ttk.Label(config_frame, text="程序图标:")
icon_label.grid(row=1, column=0, sticky="w", pady=5)
self.icon_entry = ttk.Entry(config_frame)
self.icon_entry.grid(row=1, column=1, sticky="ew", padx=5)
icon_choose = ttk.Button(config_frame, text="选择图标", command=self.choose_icon, width=10)
icon_choose.grid(row=1, column=2)
# 选择是否添加版本信息
version_set = ttk.Button(config_frame, text="设置版本信息", command=self.add_version, width=10)
version_set.grid(row=2, column=0)
self.version_var = tk.BooleanVar()
version_add = ttk.Checkbutton(config_frame, text="添加版本信息", variable=self.version_var)
version_add.grid(row=2, column=1, sticky="w", padx=5)
# 选择输出目录
output_label = ttk.Label(main_frame, text="输出目录:")
output_label.grid(row=3, column=0, sticky="w", pady=3)
self.output_entry = ttk.Entry(main_frame)
self.output_entry.grid(row=3, column=1, sticky="ew", padx=5)
output_button = ttk.Button(main_frame, text="选择目录", command=self.choose_output)
output_button.grid(row=3, column=2, padx=5)
# 显示日志区域
report_frame = ttk.LabelFrame(main_frame, text="打包日志", padding=5)
report_frame.grid(row=4, column=0, columnspan=3, pady=10, sticky="nsew")
report_frame.grid_rowconfigure(0, weight=1)
report_frame.grid_columnconfigure(0, weight=1)
self.report_text = tk.Text(report_frame, wrap=tk.WORD, font=('宋体', 10), height=10)
scrollbar = ttk.Scrollbar(report_frame, orient="vertical", command=self.report_text.yview)
self.report_text.configure(yscrollcommand=scrollbar.set)
self.report_text.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
# 打包按钮
self.start_button = ttk.Button(main_frame, text="开始打包", command=self.pack_start)
self.start_button.grid(row=5, column=1, pady=10)
# 打包时间统计
time_frame = ttk.LabelFrame(main_frame)
time_frame.grid(row=6, column=1)
self.count_label = ttk.Label(time_frame, text="打包时间统计:", style="Blue.TLabel")
self.count_label.grid(row=0, column=0, sticky="w")
self.time_label = ttk.Label(time_frame, text="00:00:00", style="Blue.TLabel")
self.time_label.grid(row=0, column=1, sticky="w")
# 关于程序页面
def program_about(self):
about = tk.Tk()
about.title('关于程序')
# 设置窗口居中
window_width = 570
window_height = 490
screen_width = about.winfo_screenwidth()
screen_height = about.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
about.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
about.resizable(width=False, height=False)
about_frame = tk.Frame(about, width=570, height=490)
about_frame.pack()
tk.Label(about_frame, text='PyInstaller打包exe工具', font=("宋体", 16)).place(x=150, y=20)
tk.Label(about_frame, text='使用编程语言:Python', font=("宋体", 14)).place(x=50, y=70)
tk.Label(about_frame, text='提前安装库:pip install pyinstaller', font=("宋体", 14)).place(x=50, y=120)
tk.Label(about_frame, text='版本信息文件名:version_info.txt', font=("宋体", 14)).place(x=50, y=170)
tk.Label(about_frame, text='设置版本信息:版本信息设置成功后自动保存为txt文件', font=("宋体", 14)).place(x=50, y=220)
tk.Label(about_frame, text='文件版本输入:必须输入整数', font=("宋体", 14)).place(x=50, y=270)
tk.Label(about_frame, text='打包时间统计:可以统计开始打包到结束的时间', font=("宋体", 14)).place(x=50, y=320)
tk.Label(about_frame, text='选择程序图标:选择指定ico图标(可选)', font=("宋体", 14)).place(x=50, y=370)
tk.Label(about_frame, text='创作者:www.pyhint.com', font=("宋体", 14)).place(x=50, y=420)
about.mainloop()
# 程序讲解页面
def help_window(self):
webbrowser.open("https://www.pyhint.com/article/157.html")
# 创建窗口,用于添加版本信息
def add_version(self):
self.add_window = tk.Toplevel(self.app)
self.add_window.title("添加版本信息")
# 设置窗口居中
window_width = 600
window_height = 540
screen_width = self.add_window.winfo_screenwidth()
screen_height = self.add_window.winfo_screenheight()
x = (screen_width - window_width) / 2
y = (screen_height - window_height) / 2
self.add_window.geometry('%dx%d+%d+%d' % (window_width, window_height, x, y))
self.add_window.resizable(width=False, height=False)
# 创建标题标签控件
Show_result = tk.Label(self.add_window, text=">>>添加版本信息<<<", bg = "white", fg = "black", font = ("宋体", 18), bd = '0', anchor = 'center')
Show_result.place(x = 90, y = 30, width = 400, height = 50)
# 创建一个StringVar变量并设置文件版本默认值
tk.Label(self.add_window, text="文件版本(整数):", font = ('宋体', 12), width = 20).place(x = 75, y = 120, anchor = 'nw')
# 创建四个输入框
self.entry1 = tk.Entry(self.add_window, font = ('宋体', 15), width = 4)
self.entry1.place(x = 240, y = 120, anchor = 'nw')
label1 = tk.Label(self.add_window, text=",", font = ('宋体', 12), width = 1)
label1.place(x = 290, y = 120)
self.entry2 = tk.Entry(self.add_window, font = ('宋体', 15), width = 4)
self.entry2.place(x = 310, y = 120, anchor = 'nw')
label2 = tk.Label(self.add_window, text=",", font = ('宋体', 12), width = 1)
label2.place(x = 360, y = 120)
self.entry3 = tk.Entry(self.add_window, font = ('宋体', 15), width = 4)
self.entry3.place(x = 380, y = 120, anchor = 'nw')
label3 = tk.Label(self.add_window, text=",", font = ('宋体', 12), width = 1)
label3.place(x = 430, y = 120)
self.entry4 = tk.Entry(self.add_window, font = ('宋体', 15), width = 4)
self.entry4.place(x = 450, y = 120, anchor = 'nw')
# 插入默认值
self.entry1.insert(0, "2")
self.entry2.insert(0, "3")
self.entry3.insert(0, "4")
self.entry4.insert(0, "5")
# 创建一个StringVar变量并设置开发软件的公司名默认值
company_name_value = tk.StringVar()
company_name_value.set("xxx有限公司")
tk.Label(self.add_window, text="开发软件的公司名:", font = ('宋体', 12), width = 20).place(x = 75, y = 170, anchor = 'nw')
self.company_name_entry = tk.Entry(self.add_window, textvariable=company_name_value, font = ('宋体', 15), width = 25)
self.company_name_entry.place(x = 240, y = 170, anchor = 'nw')
# 创建一个StringVar变量并设置软件描述默认值
description_value = tk.StringVar()
description_value.set("xxx管理工具")
tk.Label(self.add_window, text="软件描述:", font = ('宋体', 12), width = 20).place(x = 75, y = 220, anchor = 'nw')
self.description_entry = tk.Entry(self.add_window, textvariable=description_value, font = ('宋体', 15), width = 25)
self.description_entry.place(x = 240, y = 220, anchor = 'nw')
# 创建一个StringVar变量并设置软件版本默认值
file_version_value = tk.StringVar()
file_version_value.set("2.3.4.5")
tk.Label(self.add_window, text="软件版本:", font = ('宋体', 12), width = 20).place(x = 75, y = 270, anchor = 'nw')
self.file_version_entry = tk.Entry(self.add_window, textvariable=file_version_value, font = ('宋体', 15), width = 25)
self.file_version_entry.place(x = 240, y = 270, anchor = 'nw')
# 创建一个StringVar变量并设置产品名称默认值
filename_value = tk.StringVar()
filename_value.set("xxx.exe")
tk.Label(self.add_window, text="产品名称:", font = ('宋体', 12), width = 20).place(x = 75, y = 320, anchor = 'nw')
self.filename_entry = tk.Entry(self.add_window, textvariable=filename_value, font = ('宋体', 15), width = 25)
self.filename_entry.place(x = 240, y = 320, anchor = 'nw')
# 创建一个StringVar变量并设置软件版权声明默认值
copyright_value = tk.StringVar()
copyright_value.set("xxx有限公司版权所有")
tk.Label(self.add_window, text="软件版权声明:", font = ('宋体', 12), width = 20).place(x = 75, y = 370, anchor = 'nw')
self.copyright_entry = tk.Entry(self.add_window, textvariable=copyright_value, font = ('宋体', 15), width = 25)
self.copyright_entry.place(x = 240, y = 370, anchor = 'nw')
# 创建一个StringVar变量并设置产品名称默认值
product_name_value = tk.StringVar()
product_name_value.set("xxx产品名")
tk.Label(self.add_window, text="产品名称:", font = ('宋体', 12), width = 20).place(x = 75, y = 420, anchor = 'nw')
self.product_name_entry = tk.Entry(self.add_window, textvariable=product_name_value, font = ('宋体', 15), width = 25)
self.product_name_entry.place(x = 240, y = 420, anchor = 'nw')
# 获取文件版本输入的内容,返回一个元组
def get_entries():
# 获取每个Entry组件的内容
entry1_content = int(self.entry1.get())
entry2_content = int(self.entry2.get())
entry3_content = int(self.entry3.get())
entry4_content = int(self.entry4.get())
# 将内容组合成一个元组
content_tuple = (entry1_content, entry2_content, entry3_content, entry4_content)
return content_tuple
# 生成txt版本信息配置文件
def create_file():
# 获取文件版本输入框内的内容
numbers = [self.entry1.get(), self.entry2.get(), self.entry3.get(), self.entry4.get()]
# 检查每个输入框是否为空
for number in numbers:
if not number:
messagebox.showerror("错误", "输入的文件版本不能为空!")
return
else:
# 判断是否有效整数
try:
num_results = int(number)
except ValueError:
messagebox.showwarning("警告", "文件版本请输入有效的整数!")
return
# 获取Entry组件的内容
filevers = get_entries()
company_name = self.company_name_entry.get()
description = self.description_entry.get()
file_version = self.file_version_entry.get()
filename = self.filename_entry.get()
copyright = self.copyright_entry.get()
product_name = self.product_name_entry.get()
# 定义要写入的内容
lines = f'''
# UTF-8
VSVersionInfo(
ffi=FixedFileInfo(
# 注意:filevers和prodvers是包含4个元素的元组,可以将不需要的元素设置为0
filevers={filevers}, # 文件版本,比如2.3.4.5,鼠标悬浮在exe文件上会显示,也会在详细信息中显示
prodvers={filevers}, # 生产商
# 两个位掩码,无需更改它
mask=0x3f,
flags=0x0,
OS=0x4, # 设置此文件的操作系统,0x4表示NT,无需更改它
fileType=0x1, # 文件类型,0x1表示该文件是一个应用程序
subtype=0x0, # 文件的功能,0x0表示此类型未定义函数
date=(0, 0) # 创建日期和时间戳
),
kids=[
StringFileInfo(
[
StringTable(
u'040904B0',
[StringStruct(u'CompanyName', u'{company_name}'), # 开发软件的公司名
StringStruct(u'FileDescription', u'{description}'), # 软件描述
StringStruct(u'FileVersion', u'{file_version}'), # 软件版本
StringStruct(u'InternalName', u'{filename}'), # 内部名称
StringStruct(u'LegalCopyright', u'{copyright}'), # 软件版权声明
StringStruct(u'OriginalFilename', u'{filename}'), # 原始文件名
StringStruct(u'ProductName', u'{product_name}'), # 产品名称
StringStruct(u'ProductVersion', u'{file_version}')]) #产品版本
]),
VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) # 语言,中文简体
]
)
'''
# 获取所有输入框的内容
entries = [company_name, description, file_version, filename, copyright, product_name,]
# 检查每个输入框是否为空
for entry in entries:
if not entry:
messagebox.showerror("错误", "输入的内容不能为空!")
return
# 创建txt文件
try:
with open(version_file, 'w', encoding='utf-8') as file:
file.write(lines)
self.version_var.set(True)
self.add_window.destroy()
messagebox.showinfo("成功", f"版本信息设置成功!")
except Exception as e:
messagebox.showerror("错误", f"版本信息设置时发生错误:{e}")
# 定义“保存”按钮
save_button = ttk.Button(self.add_window, text="保存", command=create_file, width=15)
save_button.place(x = 145, y = 470, anchor = 'nw')
# 定义“取消”按钮
cancel_button = ttk.Button(self.add_window, text="取消", command=self.add_window.destroy, width=15)
cancel_button.place(x = 320, y = 470, anchor = 'nw')
# 选择要打包的Python脚本文件
def choose_file(self):
path = filedialog.askopenfilename(
filetypes=[("Python文件", "*.py"), ("所有文件", "*.*")])
if path:
self.file_entry.delete(0, tk.END)
self.file_entry.insert(0, os.path.normpath(path))
# 选择要打包的图标文件
def choose_icon(self):
path = filedialog.askopenfilename(
filetypes=[("图标文件", "*.ico"), ("所有文件", "*.*")])
if path:
self.icon_entry.delete(0, tk.END)
self.icon_entry.insert(0, os.path.normpath(path))
# 选择打包后输出的目录
def choose_output(self):
path = filedialog.askdirectory()
if path:
self.output_entry.delete(0, tk.END)
self.output_entry.insert(0, os.path.normpath(path))
# 在日志区域显示消息
def report_message(self, message):
self.report_text.insert(tk.END, message + "\n")
self.report_text.see(tk.END)
self.app.update_idletasks()
# 显示打包过程时间
def update(self):
if self.running:
self.elapsed_time = time.time() - self.start_time
self.time_label.config(text=self.format_time(self.elapsed_time))
self.app.after(1000, self.update)
# 启动打包过程
def pack_start(self):
self.elapsed_time = 0
self.time_label.config(text="00:00:00")
file_path = self.file_entry.get()
output_path = self.output_entry.get()
if not file_path:
messagebox.showerror("错误", "请选择要打包的Python脚本!")
return
if not os.path.exists(file_path):
messagebox.showerror("错误", "指定的Python脚本不存在!")
return
if not output_path:
messagebox.showerror("错误", "请选择输出目录!")
return
# 创建PyInstaller命令
cmd = ["pyinstaller", "--clean"]
# 打包命令中添加基本选项
if self.single_var.get():
cmd.append("--onefile")
if self.no_console_var.get():
cmd.append("--noconsole")
if self.version_var.get():
if os.path.exists(version_file):
cmd.extend(["--version-file", f'"{version_file}"'])
else:
messagebox.showerror("错误", "请先设置版本信息!")
return
# 打包命令中添加图标
if icon_path := self.icon_entry.get():
if not os.path.exists(icon_path):
messagebox.showerror("错误", "指定的图标文件不存在!")
return
cmd.extend(["--icon", f'"{icon_path}"'])
# 打包命令中添加输出目录
if output_path:
cmd.extend(["--distpath", f'"{output_path}"'])
cmd.append(f'"{file_path}"')
# 初始化界面状态
self.start_button.config(state=tk.DISABLED)
self.report_text.delete(1.0, tk.END)
self.report_message(">>> 开始打包命令: " + " ".join(cmd))
if not self.running:
self.start_time = time.time() - self.elapsed_time
self.running = True
self.update()
# 通过线程执行打包命令
threading.Thread(
target=self.start_command,
args=(cmd,),
daemon=True
).start()
# 开始执行打包命令
def start_command(self, cmd):
try:
process = subprocess.Popen(
" ".join(cmd),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=True,
encoding="utf-8",
errors="replace"
)
# 打包过程实时输出打包信息
while True:
output = process.stdout.readline()
if output == "" and process.poll() is not None:
break
if output:
self.app.after(0, self.report_message, output.strip())
# 打包返回结果处理
result = process.poll()
if result == 0:
if self.running:
self.running = False
self.app.after(0, self.report_message, ">>> 打包成功完成!")
self.app.after(0, messagebox.showinfo,
"完成", f"打包操作成功完成!耗时:{int(self.elapsed_time)}秒")
else:
if self.running:
self.running = False
error_message = f">>> 打包失败,错误码:{result}"
self.app.after(0, self.report_message, error_message)
self.app.after(0, messagebox.showerror,
"错误", f"打包失败,错误码:{result}")
except Exception as e:
error_message = f">>> 发生异常错误:{str(e)}"
self.app.after(0, self.report_message, error_message)
self.app.after(0, messagebox.showerror,
"异常错误", f"发生未预期错误:{str(e)}")
finally:
self.app.after(0, self.start_button.config, {"state": tk.NORMAL})
# 格式化时间
def format_time(self, elapsed_time):
hours, remainder = divmod(int(elapsed_time), 3600)
minutes, seconds = divmod(remainder, 60)
return f"{hours:02}:{minutes:02}:{seconds:02}"
# 当前模块直接被执行
if __name__ == "__main__":
# 创建主窗口
app = tk.Tk()
window = ExePack(app)
# 开启主循环,让窗口处于显示状态
app.mainloop()