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 |
---|---|---|---|---|
cv2 | 94 | cv2 | numpy | |
ddddocr | 83 | numpy, onnxruntime,Pillow… | ||
PyQt5 | 75 | 114 | PyQt5 | PyQt5-Qt5,PyQt5-sip |
llvmlite | 61 | 1 | llvmlite | / |
scipy | 48 | 88 | scipy | numpy |
numpy | 39 | 15 | numpy | / |
onnxruntime | 19 | onnxruntime | numpy… | |
pandas | 15 | 50 | pandas | numpy,python-dateutil,pytz |
matplotlib | 10 | 223 | matplotlib | numpy,pillow… |
Pythonwin | 7 | 2 | Pythonwin | 未知,内置? |
lxml | 6 | 16 | lxml | / |
PIL | 5 | 7 | PIL | / |
cryptography | 5 | cffi | ||
tcl | 3 | 830 | ||
shiboken2 | 1 | 5 | shiboken2 | / |
pytz | 0.8 | 600 | pytz | / |
Pyside2 | 0.8 | shiboken2 | ||
selenium | 0.8 | urllib3 | ||
win32com | 0.5 | 1 | win32com | 未知,内置? |
numba | 0.4 | 12 | numba | llvmlite, numpy, setuptools |
win32 | 0.3 | 5 | 未知,内置? |
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’}