文章目录
一、pyinstaller
Python作为一个脚本语言,要运行是必须有解释器的,它不能像C/C++那样编译成二进制。同样,也不能要求普通用户首先安装Python解释器、再安装依赖的包、最后运行transdocx。所以,需要把Python写好的软件打包成一个exe程序,让用户双击既可以使用。打包Python程序的最好的工具可能就是pyinstaller了。
pyinstaller是python的一个第三方模块,使用它可以将python程序打包为可执行文件,实现打包后的程序在没有python环境的机器上也可以运行。pyinstaller的安装方式可通过:pip installer pyinstaller
Pyinstaller打包方式一般分为 直接输入指令 和 利用spec文件进行打包。由于直接输入指令实际就是根据指令生成spec文件,再根据spec文件的内容进行打包操作。
总结一下pyinstaller打包的过程:
(1)pyinstaller -F -w xxx.py;
(2)修改上一把生成的xxx.spec文件,添加资源文件;
(3)pyinstaller xxx.spec 打包为exe文件。
二、pyinstaller打包步骤
此部分转载自:https://www.bbsmax.com/A/ke5jWwwVdr/
通常我们打包程序时,会遇到两种情况:
- 打包一个python脚本文件;文件少,可直接通过指令来打包。
pyinstaller -F -w main.py -i main.ico --workpath build路径 --distpath exe打包路径 -n exe名字
- 打包一个python项目程序(包含多个文件夹、py文件,以及相关资源文件)。文件多,指令会很复杂,建议生成 并修改spec文件后,通过spec配置文件打包。
pyinstaller xxx.spec
pyinstaller参数
主要参数
-F, --onefile
打包一个单个文件,如果你的代码都写在了一个py文件的话,可以使用这个命令,如果是多个py文件,就别用;
-D, --onedir
打包多个文件,在dist中生成很多依赖文件,适合以框架的形式编写工具代码,代码易于维护;
-a, --ascii
不包含unicode编码的支持(包括默认值:如果可用)
-c, --console
使用控制台子系统执行(默认),只对windows有效
-w, --windowed, --noconsole
使用windows子系统执行,当程序启动的时候不会打开命令行(只对windows有效)
-i , --icon=<File.ico>
将file.ico添加为打包的exe文件的图表,只对windows系统有效
--icon=<File.exe,n>
将file.exe的第n个图标添加为可执行文件的资源,只对windows系统有效
-n Name,--name=Name
可选的项目,生成的.spec文件的名字和exe名字
-p, --path
设置导入路径(和使用PYTHONPATH效果相似),可以使用路径分隔符(windows使用分好,linux使用冒号),制定多个目录的时候可以指定多个-p参数来设置,让pyinstaller自己去找程序的资源
--key KEY
用于加密Python字节码的密钥
--add-data
可以将一些非二进制文件添加到exe文件中进行打包,参数为格式为static;static
--distpath
指定打包后的程序存放目录,exe文件默认存放在当前目录下的dist目录中
--workpath
为输出的所有临时文件指定存放目录,默认为当前目录下的build目录
打包环境
- win10,64位(平台是Windows,Linux和macOS类似。)
- python3.7
- pyinstaller3.6
pyInstaller提供了两种不同的打包操作方式,可以实现上述两种情况下的打包需求。下面分别描述之。
打包一个py脚本程序
对于只有一个python脚本的简单程序,打包操作很方便,直接使用命令行的方式,输入相关指令即可。
对于一个摄氏温度转华氏温度的小程序(temp.py),我们可以这样做:
# 摄氏温度转华氏温度
temp = input("请输入摄氏温度:")
new_temp = 9/5 * int(temp) + 32
print(f"华氏温度为:{new_temp}F")
q = input("按任意键退出:")
-
首先,打开终端cmd, 进入temp.py文件所在的路径,输入指令:
pyinstaller -F temp.py
-
打包结束后,将在当前目录下生成两个文件夹(
bulid
、dist
)和一个文件temp.spec
,现在不需要理会文件夹bulid和文件temp.spec -
我们需要的打包后的可执行文件在文件夹dist中,双击即可运行,实现打包。
补充:如果想修改可执行文件的图标,使用指令:pyinstaller -i icon.ico -F temp.py
打包结束后,在dist文件夹下降出现temp.exe。你可能会发现它的图标并不是你想要的,这没有关系,你将它重命名或者拷贝到其他地方,你会发现它的图标立刻变成你期待的样子,祝你好运。
打包一个py项目程序
对于常用到的py项目程序,包含许多文件夹和py文件,以及配套的资源文件。这种情况下在终端中使用指令的方式打包程序本身也是可以实现的,但是此时打包操作就变得非常复杂,它需要你理解不同指令参数的确切意思,时不时你将入坑爬不起来,苦不堪言。
这段时间使用pygame写了一个像素鸟的游戏,想分享给别人体验,就使用了pyinstaller将程序打包成exe文件。这里分享给大家,希望对你能有所帮助。
这种情况下,一个简单的打包方式,就是通过pyinstaller提供的spec文件实现程序打包
。
下面通过一个基于pygame实现的FlappyBird介绍该项目的打包流程。
该项目包含六个文件夹,其中:bin、conf、core包含所有的python脚本文件,项目入口程序在bin\setup.py,所有音频文件在audios文件夹下,所有的字体文件在fonts文件夹下,所有的图片文件在images文件夹下。
- 第一步:打开终端进入FlappyBird路径下,输入指令:
pyinstaller -F bin\setup.py
,回车,程序结束后,发现当前目录下生成两个文件夹(bulid、dist)和一个文件setup.spec,现在删除两个文件夹,只保留setup.spec文件。
setup.spec如下:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['bin\\setup.py'], # 此列表存放项目设计的所有python脚本文件
pathex=['C:\\Users\\15057\\Desktop\\FlappyBird'], # 此列表为项目绝对路径
binaries=[],
datas=[], # 此列表存放所有资源文件,每个文件是一个2元组元素
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='setup', # 打包程序的名字
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True )
# 此处console=True表示,打包后的可执行文件双击运行时屏幕会出现一个cmd窗口,不影响原程序运行
# 如果想要修改程序图标,使用在EXE()中加入 icon='xxxxx', 切记:绝对路径
- 第二步:修改setup.spec文件,修改后的文件如下:
修改位置:
添加py_files列表,包含项目需要的所有python脚本
添加add_files列表,包含涉及到的所有资源文件,每个文件是2元组的形式存放
name=‘FlappBird’, 制定可执行程序名字
console=False, 制定可执行程序执行时不显示控制台窗口
icon=‘C:\Users\15057\Desktop\FlappyBird\images\flappy.ico’, 设置程序图标,ico格式文件(16*16)
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
py_files = [
'bin\\setup.py',
'conf\\settings.py',
'core\\main.py',
'core\\base.py',
'core\\bird.py',
'core\\pipe.py',
'core\\score.py',
]
add_files = [
('fonts\\font.ttf', 'fonts'),
('images\\*.png', 'images'),
('images\\flappy.ico', 'images'),
('audios\\*.wav', 'audios'),
]
a = Analysis(py_files,
pathex=['C:\\Users\\15057\\Desktop\\FlappyBird'],
binaries=[],
datas=add_files,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='FlappBird',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
icon='C:\\Users\\15057\\Desktop\\FlappyBird\\images\\flappy.ico' )
- 第三步:执行setup.spec文件。项目路径下输入指令:
pyinstaller setup.spec
,回车,程序结束后,新增dist文件夹,且该文件夹下新增FlappyBird.exe,最终,打包结束。
值得说明的一点是,使用spec文件打包程序时,使用-F或者使用-w,生成的spec文件内容有一点点不同。
这里大家注意即可,因为使用-F打包时默认只生成一个单独的可执行文件,如这里的FlappyBird.exe; 而使用-w打包时会生成一个文件夹,该文件夹里面包含一些库文件和FlappyBird.exe,这里的exe需要依赖这些库文件,即资源文件。在生成的spec文件中,会多一点内容。但是基本不影响打包流程和打包思路。
三、spec资源文件介绍
参考链接:https://www.pythonheidong.com/blog/article/753863/41f6ff97bef6a43a2c32/
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['blur_exe.py'],
pathex=['util', 'yolov5', 'yolov5\\utils', 'yolov5\\models'],
binaries=[],
datas=[],
hiddenimports=['skimage.feature._orb_descriptor_positions', 'yolov5.utils.torch_utils', 'torchvision', 'torch', 'pandas', 'skimage'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
a.datas += [('blur.ico', 'blur.ico', 'DATA')]
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='blur',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='blur.ico',
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='blur_exe',
)
变量 | 含义 |
---|---|
Analysis | 要求传入各种脚本用于分析程序的导入和依赖。内容主要包括以下四部分:scripts,即可以在命令行中输入的Python脚本;pure,程序代码文件中的纯Python模块,包括程序的代码文件本身;binaries,程序代码文件中需要的非Python模块,包括–add-binary参数指定的内容;datas,非二进制文件,包括–add-data参数指定的内容。 |
PYZ | 是一个.pyz的压缩包,包含程序运行需要的所有依赖,一般不需要修改 |
EXE | 根据上面两项生成的,里面包含图标、版本 |
COLLECT | 用于创建输出目录。生成其他部分的输出文件,COLLECT也可以没有 |
block_cipher | 加密密钥,防止exe被反编译 |
变量block_cipher,主要是防止exe被反编译。
block_cipher = pyi_crypto.PyiBlockCipher(key='123456789')
下面这边文章写的很详细,我也通过这位作者的方法成功进行了加密。
加密.
原文链接: https://blog.csdn.net/tangfreeze/article/details/112240342
Analysis参数
参数 | 含义 |
---|---|
scripts | 也是第一个参数,它是一个脚本列表,可以传入多个py脚本,效果与命令行中指定多py文件相同,即py文件不止一个时,比如“pyinstaller xxx1.py xxx2.py”,pyinstaller会依次分析并执行,并把第一个py名称作为spec和dist文件下的文件夹和程序的名称。 main文件入口,或者你执行文件的时候要执行多少个python文件,那就写几个,比如pyinstaller hello.py hello2.py,会依次去执行 |
pathex | 默认有一个spec的目录,当我们的一些模块不在这个路径下,记得把用到的模块的路径添加到这个list变量里。同命令“-p DIR/–paths DIR”. 意思是项目需要从什么地方导入自定义库 |
datas | 项目需要用到什么数据,比如图片,视频等。里面格式为tuple,第一个参数是文件路径,第二个是打包后所在的路径。 作用是将本地文件打包时拷贝到目标路径下。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”。 |
binaries | 添加二进制文件,也是一个列表,定义方式与datas参数一样。没具体使用过。同命令“–add-binary”。 |
hiddenimports | 指定脚本中需要隐式导入的模块,比如在__import__、imp.find_module()、exec、eval等语句中导入的模块,这些模块PyInstaller是找不到的,需要手动指定导入,这个选项可以使用多次。同命令“–hidden-import MODULENAME/–hiddenimport MODULENAME”。项目需要用到什么数据,比如图片,视频等。里面格式为tuple,第一个参数是文件路径,第二个是打包后所在的路径。 |
hookspath | 指定额外hook文件(可以是py文件)的查找路径,这些文件的作用是在PyInstaller运行时改变一些Python或者其他库原有的函数或者变量的执行逻辑(并不会改变这些库本身的代码),以便能顺利的打包完成,这个选项可以使用多次。同命令“–additional-hooks-dir HOOKSPATH”。 |
runtime_hooks | 指定自定义的运行时hook文件路径(可以是py文件),在打好包的exe程序中,在运行这个exe程序时,指定的hook文件会在所有代码和模块之前运行,包括main文件,以满足一些运行环境的特殊要求,这个选项可以使用多次。同命令“–runtime-hook RUNTIME_HOOKS”。 |
excludes | 指定可以被忽略的可选的模块或包,因为某些模块只是PyInstaller根据自身的逻辑去查找的,这些模块对于exe程序本身并没有用到,但是在日志中还是会提示“module not found”,这种日志可以不用管,或者使用这个参数选项来指定不用导入,这个选项可以使用多次。同命令“–exclude-module EXCLUDES”。假如你用的python有很多库,但是你不需要用到某个,那么就把它添加到里面去,比如很多人没有用到PyQt5, pandas, excludes=[‘PyQt5’] |
exe参数
参数 | 含义 |
---|---|
console | 设置是否显示命令行窗口,同命令-w/-c。 |
icon | 设置程序图标,默认spec是没有的,需要手动添加,参数值就是图片路径的字符串。同命令“命令-i/–icon”。 |
四、问题补充
1. –hidden-import导入找不到的(隐式调用)包
该部分转载自: https://blog.csdn.net/qq_39621009/article/details/122308590
pyinstaller -option xxx.py
options的详情可参考官方帮助文档
下面只简要介绍常用参数:
首先讲一讲pyinstaller的用法,在你想放置应用的文件夹下打开cmd,pyinstaller + 参数 +文件入口或打包定义文档。
-F:仅仅生成一个文件,不暴露其他信息,启动较慢。
-D:生成一个文件夹,里面是多文件模式,启动快。
-w:窗口模式打包,不显示控制台。
-c:跟图标路径,作为应用icon。
–hidden-import 应用需要的包,但是没有被打包进来,这里的错误是最多的,因为一般是第三方包隐式调用其他包,然后打包出来的程序显示Fate Error不能运行。或已经装过的包,跑exe还是找不到,可以在这里加,然后重新编exe。
其中,-d生成一个文件目录包含可执行文件和相关动态链接库和资源文件等;-f仅生成一个可执行文件。
对于打包结果较大的项目,选用-d生成目录相比单可执行文件的打包方式,执行速度更快,但包含更加多的文件。本文的例子选中-D方式打包。
我用到的也就是这些了,还是讲讲流程。最开始是打包日志里报一些xxx module找不到,这些pip安装就行了,关于环境问题,我建议用python3.5,因为据说3.6容易打包失败。但是我用的一开始就是3.5,pyinstaller是3.4,pyqt5.9这都没有问题,解决完这些明面上的包,还是启动不了的话,就是隐式调用了一些包,我的是sklearn的一些包和pymysql的包隐式调用而我的程序里没导入所以一直启动不了,但是也不要什么都import * 那样会导致打出来的包很大。
这里是和百度学的,就是先默认打包,不用写参数,让错误信息打到控制台,但是有个问题,控制台闪了一下就退出了,看不清。这样就需要先录下来,再去看了,下面是我当时录下来的两个截图,可以看到就是包导入的问题,明确的问题所在,就可以改进了。
改进的手段是添加–hidden-import参数。可参考上面的spec文件内容。
pyinstaller -F -w -i E:\lvtongEazyVersion\image\lvtong.ico .\lvtonguilogic.py,一开始不需要加入–hidden-import参数,我建议先生成spec文件,然后打开文件在里面进行编辑,如加入hiddenimports=[‘cython’,‘sklearn’,‘sklearn.ensemble’,‘sklearn.neighbors.typedefs’,‘sklearn.neighbors.quad_tree’,‘sklearn.tree._utils’,‘scipy._lib.messagestream’,‘pymysql’],最后,pyinstaller .\lvtonguilogic.spec,记得先删除原来的打包文件夹,不删除的话覆盖的时候可能显示无权限操作。这里还有一个小坑点,就是那个icon可能不会立刻显示,我打了好几遍都不管事,我是用的单文件打包的,最后把它粘贴出去就显示了,我觉得其实就是在dist文件夹下的显示问题。最后,可能有相对路径的资源会失效,我们新建一个文件夹,把资源放在对应的位置就好啦,如图。
2. pyinstaller 打包多进程、线程问题
在windows下 用 pyinstaller 打包好exe后,双击运行,会出现无限循环的进入主程序的情况。
原因大概是:
Windows不支持fork函数(linux是直接从主进程fork一个子进程),所以一般都是新建一个全新的跟主进程不相关的进程,因此需要 通过打包和管道传递(主进程中)执行的代码等等,mp.freeze_support()
这个函数就是判断正在运行的进程是否应该运行通过管道传递来的代码。
此时只要在调用多进程的前面加上如下的代码即可:
if __name__ == '__main__': # 主要得加这句和下面这句。
mp.freeze_support() # 这句得加上,要不打包的程序就进不了下面的子进程了。
# 示例进程
p1 = mp.Process(target=callback, target=(, ))
p1.start()
解析:
在主模块的if __name__ == '__main__' 行之后直接调用此函数。
如果省略 freeze_support() 行,则尝试运行冻结的可执行文件将引发 RuntimeError 。
知识点:
只适应于Windows系统!
在 Windows 以外的任何操作系统上调用时,调用 freeze_support() 无效。(linux也不会有这个问题)- 因为
开启子进程是不支持打包exe文件的
,所以会不停向操作系统申请创建子进程,而这个代码multiprocessing.freeze_support()
作用就是支持打包到Windows的EXE文件
。 - 多进程的程序运行后,如果直接关闭控制台窗口,那么整个程序都会退出。如果是 进入 任务管理器,单独结束 控制窗口 的进程,如果子进程不是守护进程,那么子进程还是会继续运行。
- 如果是
多线程的,则没这个问题,可以直接打包
。
但有个小提示, 如果是tkinter
的图形界面运行的,是多线程的话,如果子线程不是守护线程,那么关掉主界面后,子线程会继续运行。
如果也是控制台窗口的话,效果和上面 2 提到的进程效果一样。