Python UI设计学习笔记,打包EXE笔记:使用 `pyinstaller` 打包

打包EXE笔记:使用 pyinstaller 打包 python+pyside6 程序

1、实现步骤

  • 单目录模式运行(-D/–onedir, 默认、推荐模式)
    • 在命令终端中,输入: pyinstaller -D main.py,执行完之后,到dist文件夹中找到 main.exe 运行。
    • 添加icon程序图标,在 pyinstaller 生成的 spec 规格文件中,exe命令内容中添加 icon='icon.ico'
    • 取消exe运行时控制台的显示,在 spec 文件中,exe命令内容中修改 console=True -> console=False
    • 输入终端命令:pyinstaller main.spec, 测试新生成的 main.exe ,然后把icon文件复制一份到与 main.exe 同目录下,观察图片和控制台是否成功设置。
  • 单文件模式(-F/–onefile)
    • pyinstaller -F main.py
  • 单目录模式运行时,图标文件资源打包设置(优点:不需要使用冻结路径的方式)
    • spec文件中,设置打包时一并将资源文件复制到指定目录下。
    datas=[('.\\*.ico','.\\'),('gui\\images\\icons','gui\\images\\icons')],   # 增加程序图标以及控件图标文件
  • 效果
    在这里插入图片描述

2、使用第三方打包工具 Auto Py to Exe 打包

  • 第一步下载安装pip包,-i可以指定下载源(如清华源):
    pip install auto-py-to-exe -i  https://pypi.tuna.tsinghua.edu.cn/simple
  • 安装过程
    在这里插入图片描述
  • 单目录EXE输出,启动效果:
    在这里插入图片描述

3、参考资料

其他阅读:不推荐使用 单文件模式 的原因(从参考资料中copy)

之所以强调这一点,并不是因为单文件模式存在什么无法解决的问题。如果你非常清楚该模式的运行机制,并且在写代码的时候小心避开这些坑的话,那么所有问题都是可以避免的。但实际上,可以说 PyInstaller 的用户 99% 都达不到这个要求,而只要你写的程序有点规模的话,几乎无一例外会踩到坑里。基于这种考虑,我从来不推荐用户使用单文件模式。如果你认真看过本文,并非常肯定自己能避开下面提到的问题,那么请使用单文件模式无妨。否则,还是老老实实的使用默认模式吧。

有个问题你不妨考虑一下:我们把程序编译成了单一的可执行文件,但是从上面的单目录模式结果可以知道,要让程序运行还需要其他很多的辅助文件,此外我们自己也可以添加数据文件(–add-data)和二进制文件(–add-binary),那么这些文件哪里去了?你如何访问这些文件?

这才是秘密所在!本质上,Python 是解释程序,而不是 native 的编译程序,它并不能真正产生出真正单一的可执行文件。PyInstaller 这里变了个小戏法,如果我们使用单文件模式的话,那么 PyInstaller 生成的实际上类似于 WinZIP/WinRAR 生成的自动解压程序。它需要先把所有文件解压到一个临时目录(通常名为_MEIxxxx,xxxx是随机数字),再从临时目录加载解释器和附属文件。程序运行完毕后,如果一切正常,那么它会把临时目录再悄悄删除掉。

为了让这个过程顺利执行,PyInstaller 会对运行时的 Python 解释器做一些修改,特别是下面两个变量:

sys.frozen 如果你直接运行 Python 脚本的话,那么该变量是不存在的。但 PyInstaller 则会设置它为 True(不论单目录还是单文件模式)。因此,你可以用它来判断程序是手工运行的,还是通过 PyInstaller 生成的可执行文件运行的;

sys._MEIPASS 如果使用单文件模式,该变量包含了 PyInstaller 自动创建的临时目录名。你可以用 --runtime-tmpdir 命令行开关来强制使用特定的目录,但是鉴于最终用户有哪些目录不在程序员控制范围内,通常还是应该避免使用它。
我们可以自己写一个程序来验证:

import sys
import os

print('__file__:', __file__)
print('sys.executable:', sys.executable)
print('sys.argv[0]:', sys.argv[0])
print('os.getcwd():', os.getcwd())
print('sys.frozen:', getattr(sys, 'frozen', False))
print('sys._MEIPASS:', getattr(sys, '_MEIPASS', None))
input('Press any key to exit...')

把该脚本编译到单文件模式,然后执行。注意,先不要按任何键(否则程序退出,临时目录就不存在了),然后根据输出结果,可以到资源管理器中找到对应的临时目录:

单文件模式临时目录

你可以看到临时目录包含了运行输出所需的各种辅助文件,除了主程序.EXE 之外。仔细分析一下,我们也能明白为什么单文件模式下容易出错了。尽管 PyInstaller 努力使得各种输出和直接运行脚本的结果尽可能相似,但差别还是很明显的:

file 指向的脚本名不变,但该文件已经不存在于磁盘上了。这使得依赖于 file 去解析相对文件位置的代码非常容易出错。这也是绝大多数错误的来源,请务必注意!
sys.executable 不再指向 Python.exe,而是指向生成的文件位置了。如果你使用该变量判断系统库位置的话,那么也请小心;
os.getcwd() 指向执行文件的位置(双击运行的话是这样,但如果从命令行启动的话则未必)。但请注意,你添加的数据/二进制文件并非位于此目录,而是在临时目录上,不明白这一点的话,也很容易出现找不到文件的问题。
需要说明的是,上述问题不只存在于你自己写的代码里。有相当多的库没有考虑到在 PyInstaller 打包后下执行的场景,它们在使用这些变量的时候很有可能会出问题。事实上这也是 PyInstaller 添加 Runtime Hook 机制的一个重要原因。

如果你的脚本需要引用辅助文件路径的话,那么一种可能的形式如下:

if getattr(sys, 'frozen', False):
    tmpdir = getattr(sys, '_MEIPASS', None) 
    if tmpdir:
        filepath = os.path.join(tmpdir, 'README.txt')
    else:
        filepath = os.path.join(os.getcwd(), 'README.txt')
else:
    filepath = os.path.join(os.path.dirname(__file__), 'README.txt')

上述代码并不是唯一可行的代码,或许也不是最简洁的,但是你应当明白了,要正确处理该过程并不是轻而易举的事情。很多用户之所以出错又找不到问题,就是因为他们根本不清楚临时目录这回事,也不知道上哪里去找这些文件。如果使用单目录模式的话,那么文件在哪里是可以直接看到的,出现问题的可能性就小多了,即使有问题也很容易排查。这就是我为什么强烈推荐用户不要使用单文件模式的原因————除了看起来比较清爽之外,单文件模式基本上没有其他好处,而且它带来的麻烦比这一点好处要多太多了。

除此之外,单文件模式还带来了其他一些负面效应:
因为有临时目录和解压文件这个过程,所以单文件模式的程序启动速度会比较慢。对于稍大的程序,这个延迟是肉眼可以感觉到的;
如果你的程序运行到一半崩溃了,那么临时目录将没有机会被删除。日积月累的话,可能会在临时目录下遗留一大堆 _MEIxxxx 目录,占用大量磁盘空间。
或许对你来说上面这两个问题并不是特别重要,但知道它们的存在还是有好处的。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值