由于用户使用Python脚本的时候可能没有运行环境,所以需要打包。记录下碰到的问题。

安装

# 安装
pip install --upgrade pyinstaller

# 安装最新开发版
pip install https://github.com/pyinstaller/pyinstaller/tarball/develop
# 验证安装
pyinstaller -v

基本使用方法

Pyinstaller可以通过简单的命令进行python代码的打包工作,其基本的命令为:

pyinstaller -option xxx.py

选项

功能

-D, --onedir

Create a one-folder bundle containing an executable (default)

-F, --onefile

Create a one-file bundled executable.

高级使用方法

以一个多文件和目录的Python项目为例,项目文件包含:1.Python源代码文件;2.图标资源文件;3.其它资源文件。

1. spec文件生成

为了进行自定义配置的打包,首先需要编写打包的配置文件.spec文件。当使用pyinstaller -D/F xxx.py时候会生成默认的xxx.spec文件进行默认的打包配置。通过配置spec脚本,并执行pyinstaller xxx.spec完成自定义的打包。

通过生成spec文件的命令,针对代码的主程序文件生成打包对应的spec文件

pyi-makespec xxx.py

打开生成的spec文件,修改其默认脚本,完成自定义打包需要的配置。spec文件是一个python脚本,其默认的结构如下例所示

# -*- mode: python -*-
  
block_cipher = None
  
  
a = Analysis(['fastplot.py'],
       pathex=['D:\\install_test\\DAGUI-0.1\\bin'],
       binaries=[],
       datas=[],
       hiddenimports=[],
       hookspath=[],
       runtime_hooks=[],
       excludes=[],
       win_no_prefer_redirects=False,
       win_private_assemblies=False,
       cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
       cipher=block_cipher)
exe = EXE(pyz,
     a.scripts,
     exclude_binaries=True,
     name='fastplot',
     debug=False,
     strip=False,
     upx=True,
     console=False )
coll = COLLECT(exe,
        a.binaries,
        a.zipfiles,
        a.datas,
        strip=False,
        upx=True,
        name='fastplot')

spec文件中主要包含4个class: Analysis, PYZ, EXE和COLLECT.

  • Analysis以py文件为输入,它会分析py文件的依赖模块,并生成相应的信息
  • PYZ是一个.pyz的压缩包,包含程序运行需要的所有依赖
  • EXE根据上面两项生成
  • COLLECT生成其他部分的输出文件夹,COLLECT也可以没有

2. spec文件配置

首先给出举例python项目的spec文件配置

注意注意对于在源目录下的文件或目录可以只写文件名不写路径,子目录必须使用绝对路径

# -*- mode: python -*-
import sys
import os.path as osp
sys.setrecursionlimit(5000)
  
block_cipher = None
  
  
SETUP_DIR = 'D:\\install_test\\FASTPLOT\\'
  
a = Analysis(['fastplot.py',
       'frozen_dir.py',
       'D:\\install_test\\FASTPLOT\\lib\\app\\start.py',
       'D:\\install_test\\FASTPLOT\\lib\\models\\analysis_model.py',
       'D:\\install_test\\FASTPLOT\\lib\\models\\datafile_model.py',
       'D:\\install_test\\FASTPLOT\\lib\\models\\data_model.py',
       'D:\\install_test\\FASTPLOT\\lib\\models\\figure_model.py',
       'D:\\install_test\\FASTPLOT\\lib\\models\\time_model.py',
       'D:\\install_test\\FASTPLOT\\lib\\models\\mathematics_model.py',
       'D:\\install_test\\FASTPLOT\\lib\\views\\constant.py',
       'D:\\install_test\\FASTPLOT\\lib\\views\\custom_dialog.py',
       'D:\\install_test\\FASTPLOT\\lib\\views\\data_dict_window.py',
       'D:\\install_test\\FASTPLOT\\lib\\views\\data_process_window.py',
       'D:\\install_test\\FASTPLOT\\lib\\views\\data_sift_window.py',
       'D:\\install_test\\FASTPLOT\\lib\\views\\mathematics_window.py',
       'D:\\install_test\\FASTPLOT\\lib\\views\\para_temp_window.py',
       'D:\\install_test\\FASTPLOT\\lib\\views\\mainwindow.py',
       'D:\\install_test\\FASTPLOT\\lib\\views\\paralist_window.py',
       'D:\\install_test\\FASTPLOT\\lib\\views\\plot_window.py'],
       pathex=['D:\\install_test\\FASTPLOT'],
       binaries=[],
       datas=[(SETUP_DIR+'lib\\icon','lib\\icon'),(SETUP_DIR+'data','data'),('policy_check_rule.xlsx','.') ],
       hiddenimports=['pandas','pandas._libs','pandas._libs.tslibs.np_datetime','pandas._libs.tslibs.timedeltas',
       'pandas._libs.tslibs.nattype', 'pandas._libs.skiplist','scipy._lib','scipy._lib.messagestream'],
       hookspath=[],
       runtime_hooks=[],
       excludes=[],
       win_no_prefer_redirects=False,
       win_private_assemblies=False,
       cipher=block_cipher)
                    
       
pyz = PYZ(a.pure, a.zipped_data,
       cipher=block_cipher)
exe = EXE(pyz,
     a.scripts,
     exclude_binaries=True,
     name='fastplot',
     debug=False,
     strip=False,
     upx=True,
     console=True , icon='myicon.ico')
coll = COLLECT(exe,
        a.binaries,
        a.zipfiles,
        a.datas,
        strip=False,
        upx=True,
        name='fastplot')

a). py文件打包配置

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

b). 资源文件打包配置

资源文件包括打包的python项目使用的相关文件,如图标文件,文本文件等。对于此类资源文件的打包需要设置Analysis的datas,如例子所示datas接收元组:datas=[(SETUP_DIR+'lib\icon','lib\icon'),(SETUP_DIR+'data','data')]。元组的组成为(原项目中资源文件路径,打包后路径),例子中的(SETUP_DIR+'lib\icon','lib\icon')表示从D:\install_test\FASTPLOT\lib\icon下的图标文件打包后放入打包结果路径下的lib\icon目录。例子中('policy_check_rule.xlsx','.')表示从源码目录打包文件直接放入结果路径下。

c). ICON 图标配置

图标文件在EXE类中添加icon='myicon.ico'属性进行设置。

d). Hidden import配置

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

e). 递归深度设置

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

import sys
sys.setrecursionlimit(5000)

f). 去除不必要的模块import

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

excludes=['zmq']

打包后执行程序提示找不到文件

注意该案例只适用打包成单个目录中多个文件,不适用于打包成单个exe文件。

打包完毕之后执行程序提示找不到文件:

Please input configuration file or file path and name:
Traceback (most recent call last):
  File "main.py", line 180, in <module>
  File "main.py", line 118, in main
  File "main.py", line 80, in neteye_cfg_file_read
FileNotFoundError: [Errno 2] No such file or directory: '.\\cfg_file\\'
[17792] Failed to execute script main

这十有八九和你Python程序书写有BUG相关,因为你再语句中使用了相对路径。但是当执行程序的时候所处的目录却并非程序文件所在路径,所以要使用os.chdir()方法变更当前工作目录

# 获取程序所处目录,并且变更当前工作目录
cwd = os.path.dirname(os.path.realpath(sys.argv[0]))
os.chdir(cwd)

一些其他的BUG记录

判断文件是否存在,但是直接回车提示错误。因为直接回车当前目录是存在的,但是只能读取文件所以报错。判断一下是否文件即可解决。

if(os.path.exists(file_name) and os.path.isfile(file_name)):