1 概述

python脚本运行需要python,以及依赖的各种库。制作Exe程序,就是将该脚本、python程序及其依赖的第三方库打包成一个可执行文件。
这就使用到一个专用工具,Pyinstaller。该程序本身也是python下的一个程序,需要在python的环境中安装该程序,然后进行打包。

2 安装pyinstaller

与其他python下的程序安装类似,详细参考我另一篇文章:https://blog.csdn.net/kevinshift/article/details/88807868。

3 打包示例

进入要打包的环境中,输入以下命令

(tensorflow) C:\Users\Kevin>pyinstaller -F PythonTester.py

其中-F表示只打包成一个Exe文件。
出现如下命令时表示成功。
在这里插入图片描述
成功后默认会生成以下文件:
(1)pyTest.exe,默认位于命令行当前路径下的dist文件夹中。上例中位于:C:\Users\Kevin\dist 中。
(2)在C:\Users\Kevin中还会生成一个build文件夹,其中给出了该程序中间生成的一些东西。其中有一个文件提示了没有找到的库、被忽略的库等信息,但这只是作为一个参考。没找到会被忽略了,并不一定影响最后程序的运行,还要看最后exe的结果来说。
(3)PythonTester.spec文件。在C:\Users\Kevin中生成一个spec文件,默认名称与py文件同名,上例中为PythonTester.spec。该文件后面会详细说明。

4 打包生成文件形式

pyinstaller打包文件包含两种情况:
(1)将py文件、python及第三方库全部打包为一个单独的Exe中。

(2)将以上三者打包形成一个文件夹,文件夹中包含一个Exe,一个python,及其依赖的第三方库。
二者通过不同的选项
二者的优劣对比:
(a)启动时间
单一可执行文件比文件夹的启动时间要长
因为当程序运行时,单一的可执行文件需要解压程序的第三方依赖文件到临时文件夹中。
(b)文件结构
单一可执行文件的文件结构和工程目录是一样的,但是生成文件夹就不一样了,若程序中包含相对路径,这个相对路径自然基于的是文件夹目录,这点需要注意。
在打包过程出现问题时,可以生成文件结构,进入细致查看发生了什么。

5 pyinstaller文件如何打包

5.1从哪打包

将python文件、python程序及程序相关的第三方库都打包。这样该包就可以在别的地方独立运行了。
python文件的打包是基于某个运行环境的,因此必须切到那个环境下,上例中我们在tensorflow中。而环境的第三方库位于环境(环境位于C:\ProgramData\Anaconda3\envs)的Lib\site-packages文件夹下。所以本例中就是位于:C:\ProgramData\Anaconda3\envs\tensorflow\Lib\site-packages

5.2 找哪些文件,如何找的

打包过程中,pyinstaller不会将所有的都打包,而是将本文件运行所需要的第三方库打包的。
当然,还有我们自己的程序运行,可能也需要一些配置文件等,有二进制文件、文本文件等形式。这些如何打包呢?
第三方库、连同自己的文件,有几种形式,pyinstaller按照不同方法得到:
(1)python文件。
这个是通过分析该python文件,分析其显示import的库,并递归的去寻找这些python文件,依次将其包含。
当然这里有一个问题,对于隐式调用的则不能被包含。此时打包后的exe将会出现无法找到某些库的情况。此时需要手动加入包含模块,方法见spec文件的hiddenimports介绍(当然也可以命令行、等效的)。
在打包的过程中,这个是问题最多的地方,表现形式也多种多样,但很多都是这种情况引起的,在另一篇文章中将介绍tensorflow中隐式调用的模块的情况。
(2)二进制文件
二进制文件是无法通过上面的方法分析到的,必须通过binaries指定,告知从哪打包到哪。详见spec文件中对于binaries的介绍。
(3)其他数据文件
跟二进制文件类似,通过datas指定,详见spec中的datas介绍。

6 打包程序运行原理

6.1 带文件夹的Exe运行原理

其实这个文件夹就是一个小型的环境,是之前那个打包的环境的一个子集,可以看下他的目录结构,跟环境是很像的。
Exe中将python文件已经封装了,其中有指定运行该文件,然后调用python和第三方库。
其中python文件和第三方库,自己的配置文件等都放在文件夹下。
而Exe封装了对于python文件的逻辑和调用pthon的这个运行逻辑。
运行exe,将完成调用python程序,执行python脚本。

6.2 单独Exe运行原理

Exe中增加了一个类似于代理或boot的程序段,运行后,他会将上面所说文件夹中的python、第三方库、配置文件等东西解压到一个临时文件夹中。
解压之后,就跟上面带文件夹的情况类似了。
之后再调用pthon,运行python脚本。
当关闭该程序时,Exe会将临时文件夹删除。
默认情况下,这个临时文件夹的路径位于:C:\Users\KevinAppData\Local\Temp文件夹下,这个临时文件夹名称为_MEIxxxxxx,其中XXXX为一个随机数。
注意:正常情况下,这个临时文件夹会被删除,但是如果exe被意外强制关闭,将无法被删除,那么你的c盘就会被占用。笔者之前就是遇到该中情况,该exe被我的程序调用,后被程序强制关闭。
也可以更改这个文件夹的路径,方法通过–runtime-tmpdir设置,或者在spec文件中runtime_tmpdir中设置(和exe同一个文件夹下,设置为‘.’)。

7使用Spec文件打包

7.1 概况

打包时有很多控制选项,可以通过打包命令行使用。也可以通过Spec文件指定这些选项,这样不需要每次输入长长的选项了。效果上二者是等效的。
其实在命令行打包时,pyinstaller会自动的生成一个spec文件,保存在命令行的当前路径下。
(1)使用spec文件

(tensorflow) C:\Users\Kevin>pyinstaller PythonTester.spec

(2)生成Spec文件

(tensorflow) C:\Users\Kevin>pyinstaller -F PythonTester.py

如运行上面的命令时,则在C:\Users\Kevin下自动生成一个名称为PythonTester.spec的文件,其内容如下:

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

block_cipher = None

a = Analysis(['X:\\python\\PythonTester.py'],
             pathex=['C:\\Users\\Kevin'],
             binaries=[],
             datas=[],
             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='PythonTester',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True )

7.1.1 额外拷贝文件datas和binaries

其中datas和binaries注意,这是一个键值对,可以枚举一个或多个。
其中,前边的表示拷贝的文件,第二个表示拷贝的路径。

#注意,必须有'.'。否则报错:ValueError: too many values to unpack (expected 2)
#下面这个表示将文件\lib\general.pyc拷贝到当前文件夹下,就是解压的__MIE...等
binaries=[(r'\lib\general.pyc','.')],	

#下面表示将\lib\general.xml拷贝到.\data文件夹下
datas=[(r'\lib\general.xml',r'.\data')],

#还可以整个文件夹的拷贝,或者一类文件的拷贝。如下设置了多个规则的
datas= [ ('/mygame/sfx/*.mp3', 'sfx' ) ,	#/mygame/sfx/文件夹下所有mp3
		( '/mygame/data', 'data' ),			#/mygame/data文件夹下所有文件
		( 'src/README.txt', '.' ),
		],

7.1.2 闪屏设置

对于所有文件压缩到一个exe中的情况,解压exe需要时间,此时可以设置闪屏图片,表示正在运行。方法,在spec文件中加入:

a = Analysis(['D:\XXX\\ct.py'],
             pathex=[],
             binaries=[)
				],
             datas=[(r'D:\XXX\coco128.yaml', r'.\data' )],
             hiddenimports=[],
             hookspath=[],
             hooksconfig={},
             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)

splash = Splash(r'D:\XXX\Assets\CompanyLogo.gif',
                binaries=a.binaries,
                datas=a.datas,
                text_pos=(10, 50),
                text_size=12,
                text_color='black')

exe = EXE(pyz,
          a.scripts,
		  splash,                   # <-- both, splash target  这里引用前面的target splash
          splash.binaries,          # <-- and splash binaries	引用前面的target splash中的binaries,闪屏时各种加载的文件会逐个的显示,显示正在加载的东西。可注释掉,就不显示了
          a.binaries,
          a.zipfiles,
          a.datas,  
          [],
          name='LostDistrictIdentify',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=".",
          console=False,
          disable_windowed_traceback=False,
          target_arch=None,
          codesign_identity=None,
          entitlements_file=None )

7.2 基于tensorflow 2.0的python程序的一个例子

关于基于tensorflow 2.0的程序打包,有许多大坑,具体的介绍见本人的另一篇文章:https://blog.csdn.net/kevinshift/article/details/104888603

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

#下面这个设置了查找的栈深度,作者遇到了报RecursionError: maximum recursion depth exceeded情况,靠这个解决的。默认1000深度。见https://blog.csdn.net/ljt350740378/article/details/96134208
import sys
sys.setrecursionlimit(1000000) #例如这里设置为一百万

block_cipher = None

a = Analysis(['X:\\\\python\\Projects\\DET.py'],
             pathex=['C:\\Users\\Kevin'],	#这是放dist的Exe等的地方
             
             #这是添加二进制文件的地方,其中第三方tensorflow的二进制文件就是靠这个解决的
             binaries=[(r'C:\ProgramData\Anaconda3\envs\tensorflow4\Lib\site-packages\tensorflow_core\lite\experimental\microfrontend\python\ops\_audio_microfrontend_op.so',r'.\tensorflow_core\lite\experimental\microfrontend\python\ops')
             ],
             datas=[],	#放用户自己的配置文件
			
			#隐藏的N多个模块。注意这里不能用通配符。在hook文件中的该标签是可以的
             hiddenimports=['pkg_resources.py2_warn','tensorflow','tensorflow_core.python','tensorflow_core.python.platform',
             		'tensorflow_core.python.platform.tf_logging','tensorflow_core.python.platform.self_check',
             		'tensorflow_core.python.platform.build_info','tensorflow_core.python.pywrap_tensorflow',
             		'tensorflow_core.python.pywrap_tensorflow_internal','tensorflow_core.python.util','tensorflow_core.python.util.deprecation',
             		'tensorflow_core.python.util.tf_export','tensorflow_core.python.util.tf_decorator','tensorflow_core.python.util.tf_stack',
             		'tensorflow_core.python.util.tf_inspect','tensorflow_core.python.util.*','tensorflow_core.python.util.decorator_utils',
             		'tensorflow_core.python.util.ANY.*','tensorflow_core.python.util.is_in_graph_mode','tensorflow_core.python.util.tf_contextlib',
             		'tensorflow_core.core','tensorflow_core.core.framework','tensorflow_core.core.framework.graph_pb2','tensorflow_core.core.framework.node_def_pb2',
             		'tensorflow_core.core.framework.attr_value_pb2','tensorflow_core.core.framework.tensor_pb2'],
             hookspath=['.'],	#hook文件存放的路径,这里使用了当前路径,就是pathex=['C:\\Users\\Kevin']指定的路径

			#下面这几个是默认的,没用到,暂不说了
             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='DEServer',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir='.',	#临时文件夹的保存路径,该文件指定了为运行的exe的当前文件夹,即和exe同一个文件夹下。
          console=True )

几个特殊说明:

7.3 Hook文件与hookpath

(1)hookspath。
指定了hook文件的路径。hookspath告诉pyinstall到那里去搜索这些hook文件。那么pyinstaller在打包时,一旦遇到对应模块有hook文件时,要执行hook文件中的操作。
(2)hook文件
hook文件是一个python文件。hook文件用于告诉pyinstall打包对应的python模块文件时,需要执行的的hook文件中的操作。
(a)hook文件命名
hook文件与其对应的python文件的关联是通过hook文件本身的名称命名规则约定了。例如,如果我们要告诉pyinstall在打包tensorflow模块时,要执行包含工作,则该hook的命名为:hook-tensorflow.py。
(b)hook文件的内容
(i)可以时python文件任何内容。
(ii)spec文件中的内容
(iii)pyinstaller为打包专门定义了一些库,可以引用这些库,做一些特殊的操作。当然这本质上还是python文件编程内容。

来两个示例,这是本人基于tensorflow2.0 打包时用到的两个:
(a)hook-tensorflow.py

#从pyinstaller库中引用了模块
from PyInstaller.utils.hooks import collect_data_files, collect_submodules

#下面用于将以下模块下所有的子模块全部加入
hiddenimports = collect_submodules('tensorflow_core.core.framework')
hiddenimports += collect_submodules('tensorflow_core.core')
hiddenimports += collect_submodules('tensorflow_core')
hiddenimports += collect_submodules('tensorflow_core.lite.experimental.microfrontend.python.ops')

(b)hook-tensorflow.core.framework.py

hiddenimports=['pkg_resources.py2_warn','tensorflow','tensorflow_core.python','tensorflow_core.python.platform',
             		'tensorflow_core.python.platform.tf_logging','tensorflow_core.python.platform.self_check',
             		'tensorflow_core.python.platform.build_info','tensorflow_core.python.pywrap_tensorflow',
             		'tensorflow_core.python.pywrap_tensorflow_internal','tensorflow_core.python.util','tensorflow_core.python.util.deprecation',
             		'tensorflow_core.python.util.tf_export','tensorflow_core.python.util.tf_decorator','tensorflow_core.python.util.tf_stack',
             		'tensorflow_core.python.util.tf_inspect','tensorflow_core.python.util.*','tensorflow_core.python.util.decorator_utils',
             		'tensorflow_core.python.util.ANY.*','tensorflow_core.python.util.is_in_graph_mode','tensorflow_core.python.util.tf_contextlib',
             		'tensorflow_core.core.*','tensorflow_core.core.framework.*'
             		]

类似于spec文件,但这里是可以用通配符的:
(i)tensorflow_core.core.,表示所有子模块
(ii)tensorflow_core.ANY.
,表示下面的任意一个所有。

8 pyinstall的帮助文档和介绍

【1】pyinstaller官网的:
http://www.pyinstaller.org/

【2】pyinstaller官方帮助文档:https://pyinstaller.readthedocs.io/en/stable/

https://pypi.org/project/PyInstaller/

【3】其他网友的介绍:
翻译官方帮助文档部分:
https://zhuanlan.zhihu.com/p/40716095
【4】其他介绍:
http://blog.itpub.net/26736162/viewspace-2644904/

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐