Skip to content

用Python写了一个小工具,想打包给其他人用,但是他们电脑上不一定都有环境。于是想打包成可执行文件,点开就能运行。于是就使用Pyinstaller打包,中间遇到了很多问题。

首先,这是他们的官网,基本的用法这里都能找到,还有很多细节问题可能就需要百度了。

先了解Pyinstaller的基本用法,pyinstaller main.py这样就能直接打包一个文件为可执行文件。这是最基本最简单的用法,如果你的文件不是很复杂的话(比如只输出“你好时间”),用这个就够了。复杂一点就需要添加参数比如-F -D指定单文件还是单文件夹格式。

不过最好还是使用spec配置文件去指定配置,这样就不用每次去修改一大长串的命令了。下面我使用的也都是spec配置文件的方法。

Spec配置文件

本质上使用pyinstaller main.py也是会生成main.spec文件的,因此我们可以先这样得到spec文件,然后修改文件,再运行pyinstaller main.spec来执行打包。

下面是一个spec文件模板,本质上这玩意就是py文件,只是拓展名不同,所以也能在这里面写一些代码。

python
# -*- mode: python ; coding: utf-8 -*-  
block_cipher = None  
# 变量 a 是一个 Analysis 对象  
# 把要打包的脚本传给他,初始化的过程中,他会分析依赖情况  
# 最后会生成 a.pure  a.scripts  a.binaries  a.datas 这样4个关键列表,以及 a.zipped_data (不重要)  
# 其中:  
#     a.pure 是依赖的纯 py 文件,  
#     a.scripts 是要依次执行的脚本文件,  
#     a.binaries 是依赖的二进制文件,  
#     a.datas 是要复制的普通文件  
# 分析的这一步最耗时间  
# 这一部分负责收集你的脚本需要的所有模块和文件。的;hiddenimports 参数可以指定一些 PyInstaller 无法自动检测到的模块。  
a = Analysis(  
    ['pix2text_app.py'],  
    pathex=[],  # 用来指定模块搜索路径  
    binaries=[],  # 包含了动态链接库或共享对象文件,会在运行之后自动更新,加入依赖的二进制文件  
    datas=[('config.yaml', '.')],  # 列表,用于指定需要包含的额外文件。每个元素都是一个元组:(文件的源路径, 在打包文件中的路径)  
    hiddenimports=[],  # 用于指定一些 PyInstaller 无法自动检测到的模块  
    hookspath=['./hook'],  # 指定查找 PyInstaller 钩子的路径  
    hooksconfig={},  # 自定义 hook 配置,这是一个字典,一行注释写不下,此处先不讲  
    runtime_hooks=[],  # 指定运行时 hook,本质是一个 Python 脚本,hook 会在你的脚本运行前运行,可用于准备环境  
    excludes=[],  # 用于指定需要排除的模块  
    noarchive=False,  
    optimize=0,  
)  
  
splash = Splash('splash.jpg',  
                binaries=a.binaries,  
                datas=a.datas,  
                text_pos=(10, 50),  
                text_size=12,  
                text_color='black')  # 设置启动画面  
  
# 变量 pyz 是一个 PYZ 对象,默认给他传入 a.pure 和 a.zipped_data# 初始化过程中,它会把 a.pure  a.zipped_data 打包成一个 pyz 文件  
# 如果有密码,还会加密打包  
# pyz 是指生成的可执行文件的名称。它是由 PyInstaller 用来打包 Python 程序和依赖项的主要文件。  
pyz = PYZ(a.pure, a.zipped_data,  
          cipher=block_cipher)  
# 变量 exe 是一个 EXE 对象,  
# 给它传入打包好的 pyz 文件、a.scripts、程序名、图标、是否显示Console、是否debug  
# 最后他会打包生成一个 exe 文件  
# 创建 exe 文件  
exe = EXE(  
    pyz,  
    a.scripts,  
    splash,  
    [],  
    contents_directory='.',  
    exclude_binaries=True,  # 若为 True,所有的二进制文件将被排除在 exe 之外,转而被 COLLECT 函数收集  
    name='pix2text_app',  
    debug=False,  
    bootloader_ignore_signals=False,  
    strip=False,  # 是否移除所有的符号信息,使打包出的 exe 文件更小  
    upx=True,  # 是否用 upx 压缩 exe 文件  
    console=False,  # 若为 True 则在控制台窗口中运行,否则作为后台进程运行  
    disable_windowed_traceback=False,  
    argv_emulation=False,  
    target_arch=None,  
    codesign_identity=None,  
    entitlements_file=None,  
)  
# 变量 coll 是一个 COLLECT 对象,  
# 给它传入:  
#   exe  
#   a.binaries  二进制文件  
#   a.dattas   普通文件  
#   name        输出文件夹名字  
# 在实例化的过程中,会把传入的这些项目,都复制到 name 文件夹中  
# 这个对象包含了所有需要分发的文件  
# 包括 EXE 函数创建的 exe 文件、所有的二进制文件、zip 文件(如果有的话)和数据文件  
coll = COLLECT(  
    exe,  
    splash.binaries,  
    a.binaries,  
    a.datas,  
    strip=False,  
    upx=True,  
    upx_exclude=[],  
    name='pix2text_app',  
)

钩子

当项目变得复杂的时候,就会出现一些问题,比如有一些第三方包的资源文件不存在报错。这大概是因为pyinstaller默认不打包这些,所以就需要一些钩子去指定打包他们。官方集成了一些常用包的钩子文件,但是如果使用的包比较小众,可能就需要自己手动写。比如我是用的cnocr包里面就有一个label_cn.txt需要打包。可以在项目根目录下新建一个hook文件夹,然后再在里面新建一个hook-[模块名称].py的hook文件。这样打包的时候就会自动把资源文件也加进去了。

python
from PyInstaller.utils.hooks import copy_metadata, collect_data_files  
  
datas = copy_metadata('cnocr')  
datas.extend(collect_data_files('cnocr'))

Splash启动图

如果应用程序打开很慢,就需要一个启动图来提示用户应用正在打开。方法就是在spec文件里面添加。具体代码在上面的模板文件里就有。这个是官方文档Using Spec Files — PyInstaller 6.8.0 documentation。不过我在使用的时候会出现程序已经启动了,但是启动图还在的问题暂时还不知道怎么解决。

footer