PythonLearn

打包文件方案

前提知识

打包库

常见的有PyInstaller和Nuitka

此处用PyInstaller举例,Nuitka配置麻烦,而且我不在意速度和大小,所以使用更方便的选择

GUI界面

无论是PyInstaller和Nuitka,网上都有人做出来GUI界面以便打包,需要的可以自己找

打包选项

各个工具都有的选项

打包步骤

  1. 将你的项目放进虚拟环境中,确保PyInstaller只打包你需要的库,可以有效减少文件大小
  2. 安装PyInstaller,pip install pyinstaller
  3. 确定程序入口,比如main.py,执行命令pyinstaller main.py
  4. 最终文件会在dist目录下

更多选项

生成方式

默认为-D,表示生成文件夹,可指定-F生成单文件
如果是-D,可通过--contents-directory指定内容目录,默认为_internal,打包目录下就会有main.exe_internal,具体内容在_internal里, 你可以设置--contents-directory.来让所有内容都放在根目录下

注意:在以前的PyInstaller版本,还没有--contents-directory

文件名

如果打包main.py,则最终文件名为main.exe,可通过-n指定文件名

控制台

默认含控制台,可以看到print的输出,可指定-w无控制台,常用于GUI程序

添加文件

假如你有个语言文件,可以通过--add-data添加,举例:--add-data "lang.json:.",将lang.json添加到内容目录下
对于执行文件和和动态库,可通过--add-binary添加,格式一样,不同在于二进制文件会进行解析,确定他们的依赖也会被打包,能正常运行

添加/删除模块

通过--hidden-import添加模块,有的模块可能是通过__import__导入的,打包时没被检查到,导致运行时出错,需要手动指定
通过--exclude-module删除模块,假如你的代码测试时会用aaa.bbb,但运行时不会,可以:--exclude-module aaa.bbb

还有个-p选项,告诉PyInstaller你的代码在哪些目录下,比如你的源码在src下,就需要-p src确保能打包src下代码,等同于打包时的PYTHONPATH

图标和详细信息

-i FILE添加图标,--version-file FILE添加版本信息
版本信息的格式有固定需求,推荐找一个exe文件,用pyi-grab_version some.exe > v.txt读取他们的版本信息,然后修改为自己的信息

spec文件

直接运行

当你执行了一遍打包命令后,就会生成spec文件,下次运行时,直接pyinstaller your.spec就行

内部格式

其实就是一个Python文件,里面定义了打包的参数,还有几个类,可以通过自己编写内容来实现自定义打包,比如我在第一行加个print(1),运行时就能看到
具体分析一下

多个exe文件

复制全部,粘贴全部,改个变量名即可

Pyinstaller坑

单文件的本质

单文件的本质是将所有内容压缩到一个文件里,运行单文件时会将所有内容解压到临时文件中,这意味着

这会导致很多错误,举个例子

import os
import sys
print(sys.path)
if hasattr(sys, 'frozen'):
    base_path = sys._MEIPASS
    print(base_path)
    print(__file__)
    print(os.getcwd())
    print(sys.executable)
else:
    print(__file__)
    print(os.getcwd())
    print(sys.executable)

开发时输出

['D:\\PythonProjects\\Learn\\test', 'D:\\Python39\\python39.zip', ...
D:\PythonProjects\Learn\test\temp.py
D:\PythonProjects\Learn\test
D:\Python39\python.exe

可以看到sys.path正常,__file__指向当前文件,os.getcwd()正常,sys.executable为Python


单文件输出(命令为dist\temp.exe

['C:\\Users\\kgg\\AppData\\Local\\Temp\\_MEI147042\\base_library.zip', 'C:\\Users\\kgg\\AppData\\Local\\Temp\\_MEI147042\\lib-dynload', 'C:\\Users\\kgg\\AppData\\Local\\Temp\\_MEI147042']
C:\Users\kgg\AppData\Local\Temp\_MEI147042
C:\Users\kgg\AppData\Local\Temp\_MEI147042\temp.py
D:\PythonProjects\Learn\test
D:\PythonProjects\Learn\test\dist\temp.exe

可以看到sys.path的路径都是临时文件里的,如果你在执行文件的目录下放一个模块,import会找不到模块
打包后的文件会有个sys.frozen和sys._MEIPASS变量,sys._MEIPASS保存的是临时目录的路径
__file__看似指向模块的路径,实际上根本没有这个文件
os.getcwd()正常
sys.executable为执行文件

解决方法

  1. 写个util,提供根据不同环境获取路径的方法
  2. 采用目录的方式,并且内容目录为.,直接避免所有找不到资源问题

GUI程序崩溃无提示

尤其是PyQt,这个崩了没有一点提示,解决也简单,多用日志,输出文件,捕捉错误即可

Win7兼容

Win10打包的程序不一定能在Win7上运行,一般是缺少DLL或者依赖库不支持,换成要么在Win7里打包,要么换成Python3.7版本再打包

简化目录大小

打包后的内容目录下有很多dll文件,但其实这些都是系统自带的……,完全可以删掉来简化目录,但如果要确保它能在更多系统上运行,则不能删除