Python打包可执行文件之Pyinstaller

发布时间 2023-09-05 16:12:17作者: 日行一善g

一.部署

基本使用

1.安装pyinstaller,安装时会显示安装到的具体位置

pip install pyinstaller

 

2.执行,-w是运行时不显示黑屏

C:\Users\root\AppData\Roaming\Python\Python36\Scripts\pyinstaller  -F -w D:\xx.py

解释一下常用到的参数:

  • -i 为main.exe指定图标,pyinstaller -i 123.ico main.py
  • -w 不显示命令行窗口,编写GUI程序时使用此参数有用
  • -c 显示命令行窗口,与-w相反,默认含有此参数
  • -F 生成one-file的程序,生成结果是一个exe文件,所有的第三方依赖、资源和代码均被打包进该exe内
  • -y 如果dist文件夹内已经存在生成文件,则不询问用户,直接覆盖。默认:询问是否覆盖
  • -p 指定额外的import路径,类似于使用PYTHONPATH

 

成功:

会在当前目录下的dist文件夹中,C:\Users\root\dist

 

3.执行后会在当前目录下生成xx.spec配置文件,可以对其进行修改,修改完成后直接加载这个配置文件来打包

pyinstaller  xx.spec

打包带自定义模块的包

如果有自己定义的模块包,这里目录结构,均在/data下

- main.py  #主文件
- pp.py
- model/
  - a.py
  - b.py
- image/
  - a.image
  - b.image

 

1.首先用命令对主执行脚本执行,会在本地生成一个main.spce的配置文件

cd /data
pyinstaller  -F /data/main.py

 

2.修改spec文件为如下,model是在/data/model下,对照修改,这3个变量都要填

vim main.spec
py_files = [  # 所有用到的自定义脚本都要包括,python官方模块不用写
    'main.py',
    'pp.py',
    'model/a.py',
    'model/b.py',
]

add_files = [  # 源文件位置和目标文件位置
    ('image/a.image', 'image'),
    ('image/b.image', 'image'),]

a = Analysis(py_files,  # 加载脚本文件变量
             pathex=['/data'],  # 指定项目所在位置
             binaries=[],
             datas=add_files,  # 加载静态变量
             hiddenimports=[],
             hookspath=[],

 

3.进行打包,在dist下就生成了

pyinstaller main.spec

 

路径冻结问题

pyinstaller打包后会出现各种加载不到文件或者自定义模块的问题,这是因为所在路径出错,所以需要有一个路径的冻结,将main.py所在文件夹做基准。

在model文件夹下准备一个通用脚本PublicInit.py

import sys
import os

def app_path():
    """获取当前文件所在的绝对路径
  
    return:
        /data/pyscript
    """

    if hasattr(sys, 'frozen'):
        # Handles PyInstaller
        SETUP_DIR = os.path.dirname(sys.executable)
    SETUP_DIR =  os.path.dirname(__file__)
    return SETUP_DIR

 

main.py或者其它需要打包的脚本首行添加如下,将当前路径加载到path中

# 加载路径冻结和函数
import queue  # pyinstaller打包必备
import model.PublicInit as PublicInit, sys
SETUP_DIR = PublicInit.app_path()
sys.path.append(SETUP_DIR)  # 加载路径

from model.a import a  # 其它自定义模块就以当前路径为基准,其它均为相对路径了

注意事项

  • 安装

    • 安装这个命令的时候可能会没有命令,这就需要找到具体安装位置绝对路径使用
    • 不要在全局安装pyinstaller,一定要在虚拟环境中去临时安装,不然环境之间问题会导致找不到模块
  • 打包

    • 打包的脚本文件要为英文的
    • 打包的模块、资源文件只能在当前目录项目目录下面,而要打包的main.py要在根目录下
    • 使用命令打包的时候,会根据当前默认python环境做打包版本,所以最好是进入到虚拟环境再执行这个命令,锁定版本
    • 使用python2或者python3安装pyinstaller是一样的,都可以使用
    • 打包默认只支持单个文件,如果在脚本里引用了自定义的其它脚本,需要修改spec文件
  • 使用

    • 如果打出的包运行一闪而过,说明程序就是如此直接执行结束了,常见print打印之类的。
    • 如果代码里引用加载了配置文件,那执行的时候配置文件要在定义的位置,打出来的包是不包含除了import模块外的文件
    • 如果打出的包很大,比如几十M,这是因为打包时候会把当前环境安装的所有模块都打进去。如果要缩小,可以到新的虚拟环境中只安装需要的模块,再打包就会缩小很多。

二.spec配置文件

文件参数

变量 含义
a Analysis类的实例,要求传入各种脚本用于分析程序的导入和依赖。a中内容主要包括以下四部分:scripts,即可以在命令行中输入的Python脚本;pure,程序代码文件中的纯Python模块,包括程序的代码文件本身;binaries,程序代码文件中需要的非Python模块,包括–add-binary参数指定的内容;datas,非二进制文件,包括–add-data参数指定的内容。
pyz PYZ的实例,是一个.pyz文件,包含了所有pure中的所有Python模块。
exe EXE类的实例,这个类是用来处理Analysis和PYZ的结果的,也是用来生成最后的exe可执行程序。
coll COLLECT类的实例,用于创建输出目录。在-F模式下,是没有COLLECT实例的,并且所有的脚本、模块和二进制文件都包含在了最终生成的exe文件中。
block_cipher 加密密钥

参数 含义
Analysis参数scripts 也是第一个参数,它是一个脚本列表,可以传入多个py脚本,效果与命令行中指定多py文件相同,即py文件不止一个时,比如“pyinstaller xxx1.py xxx2.py”,pyinstaller会依次分析并执行,并把第一个py名称作为spec和dist文件下的文件夹和程序的名称
Analysis参数pathex 默认有一个spec的目录,当我们的一些模块不在这个路径下,记得把用到的模块的路径添加到这个list变量里。同命令“-p DIR/–paths DIR”.
Analysis参数datas 作用是将本地文件打包时拷贝到目标路径下。datas是一个元素为元组的列表,每个元组有两个元素,都必须是字符串类型,元组的第一个元素为数据文件或文件夹,元组的第二个元素为运行时这些文件或文件夹的位置。例如:datas=[(’./src/a.txt’, ‘./dst’)],表示打包时将"./src/a.txt"文件添加(copy)到相对于exe目录下的dst目录中。也可以使用通配符:datas= [ (’/mygame/sfx/*.mp3’, ‘sfx’ ) ],表示将/mygame/sfx/目录下的所有.mp3文件都copy到sfx文件夹中。也可以添加整个文件夹:datas= [ (’/mygame/data’, ‘data’ ) ],表示将/mygame/data文件夹下所有的文件都copy到data文件夹下。同命令“–add-data”。
Analysis参数binaries 添加二进制文件,也是一个列表,定义方式与datas参数一样。没具体使用过。同命令“–add-binary”。
Analysis参数hiddenimports 指定脚本中需要隐式导入的模块,比如在__import__、imp.find_module()、exec、eval等语句中导入的模块,这些模块PyInstaller是找不到的,需要手动指定导入,这个选项可以使用多次。同命令“–hidden-import MODULENAME/–hiddenimport MODULENAME”。
Analysis参数hookspath 指定额外hook文件(可以是py文件)的查找路径,这些文件的作用是在PyInstaller运行时改变一些Python或者其他库原有的函数或者变量的执行逻辑(并不会改变这些库本身的代码),以便能顺利的打包完成,这个选项可以使用多次。同命令“–additional-hooks-dir HOOKSPATH”。
Analysis参数runtime_hooks 指定自定义的运行时hook文件路径(可以是py文件),在打好包的exe程序中,在运行这个exe程序时,指定的hook文件会在所有代码和模块之前运行,包括main文件,以满足一些运行环境的特殊要求,这个选项可以使用多次。同命令“–runtime-hook RUNTIME_HOOKS”。
Analysis参数excludes 指定可以被忽略的可选的模块或包,因为某些模块只是PyInstaller根据自身的逻辑去查找的,这些模块对于exe程序本身并没有用到,但是在日志中还是会提示“module not found”,这种日志可以不用管,或者使用这个参数选项来指定不用导入,这个选项可以使用多次。同命令“–exclude-module EXCLUDES”。
exe参数console 设置是否显示命令行窗口,同命令-w/-c。
exe参数icon 设置程序图标,默认spec是没有的,需要手动添加,参数值就是图片路径的字符串。同命令“命令-i/–icon”。

实操讲解

打包 py文件

  • 针对多目录多文件的python项目,打包时候需要将所有相关的py文件输入到Analysis类里。如上的spec脚本,将所有项目中的py文件路径以列表形式写入Analysis,这里为了说明混合使用了绝对路径和相对路径。这里面的所有列表项都必须是py文件!
  • Analysis类中的pathex定义了打包的主目录,对于在此目录下的py文件可以只写文件名不写路径。

 

打包 资源文件(其实就是复制资源到打包后文件夹对应位置)

  • 资源文件包括打包的python项目使用的相关文件,如图标文件,文本文件等。

  • 对于此类资源文件的打包需要设置Analysis的datas,将非py文件的路径与存放的文件夹名写在元组里,如:datas=[(SETUP_DIR+‘lib\icon’,‘lib\icon’),(SETUP_DIR+‘data’,‘data’)]
    datas:

    第一个参数:Python中的资源文件等非py类型文件的路径
    第二个参数:打包后路径,要和路径中的文件夹名称相同
  • 例子中的(SETUP_DIR+‘lib\icon’,‘lib\icon’)表示将D:\install_test\FASTPLOT\lib\icon文件夹以及其中内容打包后还放在打包路径下的lib\icon目录
  • 假如项目某个文件夹打包时是空文件夹,但是运行中需要用到,比如log文件夹,即使将log地址写进datas中也不会生成,需要自己在代码里面用到某个文件夹去查看是否存在然后递归创建

 

Hidden import配置

  • pyinstaller在进行打包时,会解析打包的python文件,自动寻找py源文件的依赖模块。但是pyinstaller解析模块时可能会遗漏某些模块(not visible to the analysis phase),造成打包后执行程序时出现类似No Module named xxx。
  • 这时我们就需要在Analysis下hiddenimports中加入遗漏的模块,如例子中所示。
  • hiddenimports=['palettable'], # 动态引入的库或模块

 

递归深度设置

  • 在打包导入某些模块时,常会出现"RecursionError: maximum recursion depth exceeded"的错误,这可能是打包时出现了大量的递归超出了python预设的递归深度。因此需要在spec文件上添加递归深度的设置,设置一个足够大的值来保证打包的进行,即去除不必要的模块import
    import sys
    sys.setrecursionlimit(5000)

  • 有时需要让pyinstaller不打包某些用不到的模块,可通过在excludes=[]中添加此模块实现,如
    excludes=['zmq']

 

加密

上面还有个变量block_cipher,主要是防止exe被反编译。加密

block_cipher = pyi_crypto.PyiBlockCipher(key='123456789')

三.报错

1.124 INFO: UPX is not available.

这是因为UPX这个文件消失了,打包需要用的,可以卸载pyinstaller再重装。

 

2.命令行窗口,如果你使用了上面的命令打包后发现,程序打不开

在打开程序中会闪过一个报错,有句话ImportError:OpenCV loader:missing configuration file:['config.py']. Check OpenCV installation。意思缺少了相对应的库,这里是OpenCV库。

这时候需要在打包时指定 -p 参数,后面跟上 python 目录下的第三方库模板目录路径 site-packages ,再打包就成功了。

pyinstaller -i 123.ico -F -w main.py -p C:/python/lib/site-packages

 

3.打包后运行报错:ModuleNotFoundError: No module named 'queue'

需要代码最开头加载这个模块,重新打包即可,尽量不用spec的excludes

import queue

 

4.报错bash: /data/tools/python_3.6.0/bin/pyinstaller: /usr/local/python-3.6/bin/python3.6: bad interpreter: No such file or directory

这是因为python在机器内的环境变量是3.6版本,而当前用的虚拟环境是3.7版本,打包的时候pyinstaller找到的路径是3.6的就有问题

解决:

PATH=$PATH:/data/pytwo/bin/python3.7

 

5.提示没有自定义的这个model文件夹模块,再执行就一直报错了。

解答:需要将这个脚本和spec文件放到项目的根目录下,在这个目录下进行打包才行。

 

6.闪退问题

解决办法:

在打包的py文件主函数最后一行添加以下代码

 input("please input any key to exit!")

然后把exe拖进cmd命令窗口,回车运行,就能够看到报错信息了。注意打包时不能带 --noconsole参数,否则就算在cmd窗口也看不到报错信息的。例如:pyinstaller test.py --noconsole 生成的test.exe 拖到cmd窗口执行,出错了,错误信息也不会显示出来

 

7.ModuleNotFoundError: No module named 'ansible'

一直提示缺包,但pyinstaller确实是在当前虚拟环境安装了,虚拟环境也安装相关模块包了。这个问题是PATH路径指定问题,必须要把当前的虚拟环境目录/data/pyone/bin/python3.7给加到PATH里才对。

 

8.动态链接库缺失,执行程序出错:ImportError: DLL load failed: 找不到指定的模块

在打包过程中一般会有与此相关的warning提示(lib not found)无法找到这些动态链接库。例如在32位版本的打包中,可能会出现scipy模块相关的dll文件无法找到。

这时就需要在打包的spec文件中指定动态链接库路径,使其关联到打包后的路径中。Analysis下的binaries是为打包文件添加二进制文件,缺失的动态链接库可以通过这种方式自动加入到打包路径中。

binaries=[(‘C:\Program Files\Python36-32\Lib\site-packages\scipy\extra-dll’,’.’)]

 

9.当打包时出现:UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0xce in position 122

可在打包的命令行中输入chcp 65001设置命令行显示utf-8字符,然后再执行打包命令。或者,修改pyinstaller包下的compat.py,根据报错对应的行将

out = out.decode(encoding)
改为

out = out.decode(encoding, ‘replace’)

 

10.一些公共模块比如ansible,显示缺少xx.yml。这是因为非py文件不加载,就需要spec去手动传递一个。

datas=[('/data/pyone/lib/python3.7/site-packages/ansible/config', 'ansible/config')],