[Python进阶] Pyinstaller打包心得(减少打包空间占用、快捷打包、找不到资源等)

本文详细介绍了如何使用PyInstaller进行高效打包,包括避免from*import*,模块导入的最佳实践,处理新版本资源结构变化,Selenium打包注意事项,以及如何通过Spec文件管理和排除大型模块以减小程序体积。
摘要由CSDN通过智能技术生成

5.11 Pyinstaller打包心得(减少打包空间占用、快捷打包、找不到资源等)

5.11.1 说明

1、from 模块 import *这种导入模式只能在模块级别中使用。报错:仅在模块级别允许import *
2、在class中,import 模块from 模块 import 内容这两种导入方式都不会对class中的方法生效。在方法中使用会报错:未解析的引用。不过这两种导入方式可以在方法或函数中使用。
3、不同的模块有着不同的依赖项,也就是需要依赖其他的模块才能使用。对于第三方模块,可以通过命令pip show 模块进行查看,命令结果中的Requires会显示该第三方模块的依赖。而内置模块可以去官方查询其依赖。

C:\Users\admin>pip show ddddocr
Name: ddddocr
Version: 1.4.7
Summary: 带带弟弟OCR
Home-page: https://github.com/sml2h3/ddddocr
Author: sml2h3
Author-email:
License: UNKNOWN
Location: c:\users\furongbing\appdata\roaming\python\python310\site-packages
Requires: numpy, onnxruntime, opencv-python-headless, Pillow
Required-by:

4、不同的模块打包后占用的空间大小不同,而有些依赖还会有依赖。

5.11.2 模块导入技巧

1、不要一来就在模块的上方列出所有需要的模块名,尤其是那种打包后占用空间比较大,并且有很多子孙依赖的模块。
2、子孙依赖比较多、占用空间比较大、使用比较少的模块,可以在函数、方法之中导入。这样,如果用不上,则打包的时候不会打包这些模块。可以极大的减少打包后的程序体积。也可以加快打包速度。

class AAA:

    def fun1_add(self, x, y):
        import datetime
        from time import sleep
        from tqdm import trange
        sleep(1)
        print('add')
        print(datetime.date.today())
        for i in trange(10):
            sleep(0.2)
            print(i)
        print(x + y)

5.11.3 新版本pyinstaller打包资源问题

说明
自 Pyinstaller>=6.0.0 版本后,在打包 one dir(-D 目录模式)时,除可执行文件外,其余文件都将被转移到 _internal 文件夹下。
官方原文

Restructure onedir mode builds so that everything except the executable (and .pkg if
you’re using external PYZ archive mode) are hidden inside a sub-directory. This
sub-directory’s name defaults to _internal but may be configured with a new
–contents-directory option. Onefile applications and macOS .app bundles are unaffected.

问题
旧项目中凡是直接使用相对路径调用的文件,在使用 Pyinstaller>=6.0.0 版本打包后,运行可执行文件时,会找不到这些文件。
办法
1、使用 --contents-directory参数,打包时设置--contents-directory .来使其启用旧版本的one dir 布局。
在制作spec文件时,命令如下:

“pyi-makespec.exe” --contents-directory . main.py

2、将所有的资源文件都存放到一个文件夹内,这样,打包后可以一次性拷贝到程序中,方便调用。

5.11.4 Selenium打包问题

说明
由于通过selenium驱动谷歌浏览器需要用到谷歌浏览器和驱动,而打包后在其他设备上可能会出现谷歌浏览器和驱动不匹配的情况,以及出现找不到浏览器和驱动的情况。
办法
1、 打包时,将谷歌浏览器和驱动文件全部打包进项目所在的资源文件夹内
2、修改谷歌浏览器驱动代码,查找谷歌浏览器和驱动文件时,先尝试在资源文件夹内查找,未找到再在本机PythonPath中查找。

5.11.5 打包排除

由于有很多模块打包后体积非常大,又是比较基础的模块,容易被依赖,所以,有必要收集整理这种模块。
这些模块,要么体积大,要么占用文件数量比较多,不利于打包。现收集整理如下:
参照:python – 相关.xlsx工作簿中的导入工作表

文件夹名大小(MB)文件数排除名Requires
cv294cv2numpy
ddddocr83numpy, onnxruntime,Pillow…
PyQt575114PyQt5PyQt5-Qt5,PyQt5-sip
llvmlite611llvmlite/
scipy4888scipynumpy
numpy3915numpy/
onnxruntime19onnxruntimenumpy…
pandas1550pandasnumpy,python-dateutil,pytz
matplotlib10223matplotlibnumpy,pillow…
Pythonwin72Pythonwin未知,内置?
lxml616lxml/
PIL57PIL/
cryptography5cffi
tcl3830
shiboken215shiboken2/
pytz0.8600pytz/
Pyside20.8shiboken2
selenium0.8urllib3
win32com0.51win32com未知,内置?
numba0.412numballvmlite, numpy, setuptools
win320.35未知,内置?

5.11.6 Spec文件最终说明版

# -*- mode: python ; coding: utf-8 -*-


a = Analysis(
# 主要接收一系列的脚本名作为输入,它会分析所有导入的模块以及其他依赖
    ['E:\\Syncdisk\\PythonFiles\\Project\\0_打包模板\\打包测试.py'],
# 打包的主目录,对于在此目录下的py文件可以只写文件名不写路径,默认为空
    pathex=[],
    binaries=[],
# 将需要复制的资源文件拷贝到对应的文件夹中,res为资源文件夹,ddddocr需要复制过去,自动打包无法识别。
    datas=[('res/*.*','./res'), (r'C:\Users\admin\AppData\Roaming\Python\Python310\site-packages\ddddocr\*.*','./ddddocr'), (r'C:\Users\admin\AppData\Local\Google\Chrome\Application\*.*','./res/chrome')],
# 自动打包时遗漏的模块
    hiddenimports=['PySide2.QtXml'],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
# 剔除不需要的模块,用于减少打包后的程序大小,按照打包后的大小降序排序   excludes=["cv2","ddddocr","PyQt5","llvmlite","scipy","numpy","scipy.libs","onnxruntime","pandas","matplotlib","Pythonwin","lxml","cryptography","PIL","shiboken2","Pyside2","pytz","selenium","win32com","numba","certifi","win32","lib2to3"],
    noarchive=False,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    [],
# True代表打包成文件夹,False代表打包成单文件
    exclude_binaries=True,
    name='打包测试',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
# True代表打包完成打开结果程序时会带CMD窗口,建议第一次带上。
    console=True,
# 运行程序后,在桌面任务栏上的图标
icon='d:\\mysoft.ico') 
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
# Pyinstaller>=6.0.0要设置这个,否则会找不到资源文件
    contents_directory='.',
)
# 文件夹模式下,COLLECT会创建用于存放各文件的文件夹;而单文件模式下,COLLECT不会被用到,EXE会直接接收所有的脚本,模块以及二进制文件。所以,如果是-F参数(单文件)的情况下,没有这个项目。只有是文件夹模式才会有这个。
coll = COLLECT(
    exe,
    a.binaries,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name='打包测试',
)

5.11.7 一些打包时用到的实用代码

5.11.7.1 将excel表中存放在列中的模块名转换成打包排除项
def mtListToString(pmList: str):
    """将excel表中存放在列中的模块名转换成打包排除项"""
    return pmList.strip().strip('\n').split('\n')


string = """
cv2
ddddocr
PyQt5
llvmlite
scipy
numpy
"""

result = mtListToString(string)
print(result)

[‘cv2’, ‘ddddocr’, ‘PyQt5’, ‘llvmlite’, ‘scipy’, ‘numpy’]

5.11.7.2 获取给定模块的子依赖
from paSystem.mdCMD import csCmdBySubprocess

cscbs = csCmdBySubprocess()
def mtGetChildRequiresOfModule(pmModule: str):
    """获取指定模块的依赖模块"""
    command = f'pip show {pmModule}'
    result = cscbs.mtExecCmdAndOutput(pmCommand=command)
    if 'not found' in result:
        return set()
    else:
        lst1 = result.strip().strip('\n').split('\r\n')
        for lst in lst1:
            if 'Requires: ' in lst:
                if lst.split(': ')[1:][0] == '':
                    return set()
                else:
                    return set(lst.split(': ')[1:][0].split(', '))

module = 'ddddocr'
result = mtGetChildRequiresOfModule(module)
print(result)

{‘opencv-python-headless’, ‘numpy’, ‘Pillow’, ‘onnxruntime’}

5.11.7.3 获取指定模块的所有依赖
from paSystem.mdCMD import csCmdBySubprocess

cscbs = csCmdBySubprocess()


def mtGetChildRequiresOfModule(pmModule: str):
    """获取指定模块的依赖模块"""
    command = f'pip show {pmModule}'
    result = cscbs.mtExecCmdAndOutput(pmCommand=command)
    if 'not found' in result:
        return set()
    else:
        lst1 = result.strip().strip('\n').split('\r\n')
        for lst in lst1:
            if 'Requires: ' in lst:
                if lst.split(': ')[1:][0] == '':
                    return set()
                else:
                    return set(lst.split(': ')[1:][0].split(', '))


def mtGetChildrenRequiresOfModules(pmModules: list):
    """获取指定模块的所有依赖"""
    children_modules = set()
    lst = []
    for module in pmModules:
        lst.append(module)
    while lst:
        item = lst.pop(0)
        child = mtGetChildRequiresOfModule(item)
        for son in child:
            if son not in children_modules:
                lst.append(son)
                children_modules.add(son)
    return children_modules


module = ['ddddocr']
result = mtGetChildrenRequiresOfModules(module)
print(result)

{‘onnxruntime’, ‘flatbuffers’, ‘coloredlogs’, ‘pyreadline3’, ‘protobuf’, ‘humanfriendly’, ‘packaging’, ‘Pillow’, ‘opencv-python-headless’, ‘sympy’, ‘mpmath’, ‘numpy’}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0思必得0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值