使用方法
之前一直用的py2exe,到了python3.8不好用了,遂改为使用pyinstaller。使用下来,发现还是很好用的。
有两种模式:单文件和文件夹模式。
单文件模式是把所有东西都打包到一个exe里,执行的时候,解压到临时文件夹去执行,命令为:
pyinstaller --onefile my_script.py
windows下临时文件夹以_MEI打头,形如C:\Users\${user}\AppData\Local\Temp\_MEI1393882。
文件夹模式会生成一个文件夹,里面有exe+internal目录,internal目录里是exe所依赖的文件。命令为:
pyinstaller --onedir my_script.py
前者使用和发布更简单,一个exe搞定。但后者也有其作用,因为onefile模式下的exe对我们是黑盒,要了解exe内部子目录如何划分,可以查看onedir模式输出的internal目录。
常见问题
打包文件过大问题
解决该问题最有效的方法,是使用virtual env,跟python主环境隔离开,同时精简requirements.txt里的依赖,只放程序运行所必须的依赖。
资源文件无法访问的问题
要使用 --add-data选项,把运行过程中要访问的非python脚本资源打包进来,windows下,以onefile模式为例,我要把config/cfg1.json和config/cfg2.json打进exe的同层目录下,这么写:
pyinstaller --onefile --add-data "config/cfg1.json;config/" --add-data "config/cfg2.json;config/" my_script.py
可以看到,有多个–add-data选项,每个–add-data的值其实是一个pair,pair的第一项是源文件,可以是相对路径,pair的第二项是目标文件夹。
如何区分pyinstaller打包后执行还是普通脚本执行
pyinstaller打包后执行和普通脚本方式执行,其工作目录是有区别的,这会影响资源文件的读取。
因pyinstaller打包时会设置sys.frozen和sys._MEIPASS字段,可使用如下脚本区分两种场景:
def is_pyinstaller_executable():
# check sys.frozen
if getattr(sys, 'frozen', False):
return True
# check sys.argv[0] 和 sys.executable
if sys.argv[0] == sys.executable:
return True
# check sys._MEIPASS
if getattr(sys, '_MEIPASS', None):
return True
return False
if is_pyinstaller_executable():
print("run as a PyInstaller packaged executable")
WORK_DIR = os.path.dirname(os.path.abspath(sys.executable))
else:
print("run as normal py script")
WORK_DIR = os.path.dirname(__file__)
print('working dir:%s' % WORK_DIR)
上述代码,在pyinstaller打包场景,获取exe所在目录为工作目录,而在普通脚本场景,则使用入口脚本my_script.py所在目录为工作目录。
顺带说一下,pyinstaller打包场景下,使用__file__方式获得的工作目录为解压的临时目录,就是前面提到的临时目录C:\Users\${user}\AppData\Local\Temp\_MEI1393882。