赞
踩
pyinstaller打包后会形成一个文件夹或单个的exe(可以用参数指定)。但不论是哪一种情况,都会包含一个exe文件,用户可以双击它运行该应用程序。
假如你要打包myscript.py,那么打包完成后运行这个myscript.exe,效果就是运行myscript.py后的效果。
默认情况下,打包会形成一个黑色的控制台(cmd的样子),也可以设置隐藏这个控制台。
这个控制台用于为python提供标准输入(stdin),标准输出(stdout),标准错误(stderr)。也就是说,这个控制台上显示了print函数的输出,用于接收input函数的输入,还会输出python的异常。
如果你隐藏了这个控制台,程序中的print就无法显示(但是不会报错),报错信息也无法被用户直接看到(pyinstaller有一些选项来控制显示异常,后文详解);需要注意的是,此时不能使用input,否则会报错:
RuntimeError: input(): lost sys.stdin
python文件有一种后缀名*.pyw,这样的程序执行时默认会隐藏控制台。如果将文件后缀命名为pyw,那么pyinstaller也会认为它隐藏了控制台,不需要通过额外的选项来指定。
当你制作GUI程序的时候,最好选择隐藏控制台,来提升用户体验。
打包后的文件可能会被反编译(即通过exe文件得到原来的代码),可以通过一些方法进行加密(后文详解)。
下面介绍一下打包完成后形成的文件夹。
这个文件夹的名字是你提供的,一般是你要求打包的python文件的名称。文件夹中包含一个exe文件,以及其他一些依赖文件(比如一些dll文件,可能还有你的应用所需要的图片等素材)。你只需要将该文件夹压缩就能发给别人运行了。
当你运行里面的exe文件后,pyinstaller其实只是启动了解释器,然后通过解释器运行你的主程序。
打包成单个文件夹的形式便于调试,因为你可以清楚地看到pyinstaller将哪些模块文件放到了文件夹中。
当你更改代码,需要用户更新应用时,只需要让用户对于部分内容进行修改。如果你只修改了主程序,没有使用多余的模块,那么就只需要让用户替换里面的exe文件,而不用全部替换(因为更新前后使用的模块是一致的,它们都以多文件的形式放到了文件夹中)。
单个文件夹的状态下,程序的启动速度和打包前差不多。
打包成单个的文件夹后,文件大小可能会更大一些,因为大部分依赖文件没有进行压缩。
单个exe模式下,pyinstaller只会生成一个单独的exe文件,所有的依赖文件都会被压缩到exe文件中。
和上面的文件夹模式类似,exe启动后,pyinstaller也是通过调用python解释器来运行主程序的。
启动单个exe非常简单,用户只需要点击exe文件就能运行,而无需在一大堆的依赖文件中找到exe文件。并且在经过压缩后,这个exe文件的文件大小会大大减小。
单个exe的启动速度比较慢(通常会慢几秒,且只是启动时的速度,不是运行后的速度),这是因为pyinstaller会在这一段时间中将一些依赖文件写入到一个临时的文件夹(后文介绍该文件夹的调用方式)。
如果你希望添加一些附带文件(比如使用说明README),你还需要额外新建文件夹并将其放进去。
在了解相关原理后,下面正式进入打包环节。
本章介绍通过命令行参数进行打包,这种方式比较初级,适用于一般的打包方式。
打包需要通过cmd进行,语法和大多数工具一样。pyinstaller最简单的打包方式是:
pyinstaller myscript.py
其中myscript.py是你想要打包的程序。
如果这一步提示找不到myscript.py,请检查路径是否正确;如这一步提示找不到pyinstaller工具,请参考最后一章“常见问题”。
如果直接传递文件名,pyinstaller会生成一个spec文件将一些打包参数放到里面,然后进行打包。打包完成后,你会在你的目录下找到一个dist文件夹,里面存储了打包后的结果。pyinstaller还会生成一个build文件夹并写入一些日志信息。
当然,你也可以自己构建一个*.spec文件(后文介绍),然后交给pyinstaller进行处理。
本节只是列举并简要介绍常用的参数,并不过多展开,将在下面的部分对于一些重点参数举例介绍。
如有不熟悉命令行参数和命令行使用的读者可自行搜索,或者参考下面的介绍:
pyinstaller -D -i "icon.ico" myscript.py
调用命令时,首先给出工具名称(比如上面的 pyinstaller ),然后提供相关参数,有一些参数是可选的但不需要附带任何值(比如上面的 -D ),有一些参数是必选的(比如上面的 myscript.py ),有一些参数需要附带一个值(比如上面的 -i “icon.ico” )。其中有一些参数可以简写(比如 -i 就是 --icon 的简写)。
位置参数在打包时放在最后,而且无需指定关键字。
pyinstaller的位置参数是需要打包的文件路径,或是spec文件路径。
下面是比较有用的参数,读者可以自行了解,也可以跳过这部分,打包时用于参考:
参数名 | 描述 |
-D | 文件夹模式。在打包完成后生成一个文件夹,其中包含一个exe文件和一个包含若干依赖文件的文件夹(详见上文)。(默认) |
-F | 单文件模式。在打包完成后只会生成一个单独的exe文件(详见上文)。 |
–add-data <SRC;DEST or SRC:DEST> | 指定一个文件夹或文件(非二进制),将其嵌入到exe中。 |
–add-binary <SRC;DEST or SRC:DEST> | 和–add-data类似,不过指定的文件夹或文件是二进制的 |
-p DIR --paths DIR | 提供一个路径进行搜索并且导入里面的模块(不同的路径使用路径分隔符os.pathsep分隔开,或者多次使用这个参数)。 这可以解决有时候第三方模块找不到的问题。 |
–hidden-import MODULENAME --hiddenimport MODULENAME | 需要进行额外导入的模块。当pyinstaller在程序中找不到一些模块时,需要你额外指定。这个参数可以多次使用,可以解决一些模块找不到的问题。 |
–splash IMAGE_FILE | 添加一个启动画面(图片文件)路径,在程序运行前显示指定的启动图片,起到加载提示的效果。 |
-c, --console, --nowindowed | 打包程序运行后出现一个黑色的控制台窗口(默认) |
-w, --windowed, --noconsole | 打包程序运行后隐藏控制台窗口 |
-i <FILE.ico or FILE.exe,ID or FILE.icns or Image or “NONE”> --icon <FILE.ico or FILE.exe,ID or FILE.icns or Image or “NONE”> | 设置打包后exe程序的图标(只能在Windows和macOS上使用) |
–disable-windowed-traceback | 禁用异常提示(只能在Windows和macOS上使用) |
–help, -h | 打印pyinstaller的帮助信息并退出 |
下面是一个程序示例,将创建一个窗口并显示一张图片image.gif和一段提示。读者无需了解其代码细节。接下来将以这个程序为例进行一个简单的打包示范。
'''
一个简单的应用
'''
import tkinter as tk # 导入tkinter
root = tk.Tk() # 创建窗口
root.title("我的应用程序") # 更改标题
image = tk.PhotoImage(file="assets/image.gif")
label = tk.Label(root, text="你好,用户!", image=image, compound="top")
label.pack() # 显示图片
root.mainloop() # 保持窗口运行
下面是这个应用文件夹的文件层级结构:
- my_app
- assets
- image.gif
- my_app_name.py
由于这个应用不需要进行print和input这样的控制台类操作,所以我选择隐藏控制台。打开cmd并进入程序文件所在的文件夹my_app,打包时添加-w参数:
pyinstaller -w my_app_name.py
接下来会出现若干个INFO提示,如果没有错误,那么打包就成功了。
完成打包后,生成了build和dist文件夹,以及一个spec文件;dist文件夹包含打包的结果,build文件夹中是一些日志信息,spec文件里面是用于打包的配置信息。
接下来是重要的一步。由于打包时没有绑定任何的资源文件,所以此时运行时会报错,提示找不到image.gif。此时,应该把程序文件夹下的assets文件夹(参见上方的文件夹层级)复制到dist文件夹中的程序文件夹,和exe文件位于同一位置。
接下来,再试一下单文件模式的打包,只需添加-F参数:
pyinstaller -w -F my_app_name.py
打包后,生成了一个单个的my_app_name.exe,而没有其他文件。同样也需要将assets文件夹复制到与该exe文件的同一位置。
经常需要复制文件夹不仅麻烦,而且还无法防止里面的内容被用户修改。此时,我们可以使用pyinstaller的–add-data参数,将assets文件夹里面的资源嵌入到exe文件中。
资源嵌入exe只在单文件模式下使用。文件夹模式下,资源文件夹不会嵌入到exe中,但是会被复制到exe所在的文件夹。
使用资源嵌入后,资源文件夹的路径发生了变化,我们不能使用一般的相对路径来调用assets这样的内嵌资源文件夹。
前面已经讲过,pyinstaller单文件模式下的exe启动后,会将嵌入的资源文件放到一个临时的文件夹中,这个文件夹的名字不是固定的,叫做_MEIxxxxx,其中xxxxx是随机数。这个文件夹的路径在打包后会被放到sys._MEIPASS这个变量里面,只需要调用sys._MEIPASS就可以获得这个路径文件夹。
于是,我们通过以下函数返回正确的路径:
def get_path(relative_path):
try:
base_path = sys._MEIPASS # pyinstaller打包后的路径
except AttributeError:
base_path = os.path.abspath(".") # 当前工作目录的路径
return os.path.normpath(os.path.join(base_path, relative_path)) # 返回实际路径
这个函数通过一个相对的路径返回实际的绝对路径。
需要注意:sys._MEIPASS这个属性只有在打包成exe后才被创建,以py代码执行的时候这个属性是不存在的,所以要通过try…except…代码块捕获异常。如果不是pyinstaller模式,那么就使用py文件所在的文件夹的路径作为基本路径。我们不必担心这个函数的工作原理(虽然者不难理解),这个函数可以直接拿来用(是一位叫做davidpendergast的大佬写的)。
于是,我们将代码改成这样(省略了部分内容):
... import sys import os def get_path(relative_path): try: base_path = sys._MEIPASS except AttributeError: base_path = os.path.abspath(".") return os.path.normpath(os.path.join(base_path, relative_path)) ... image = tk.PhotoImage(file=get_path("assets/image.gif")) ...
接下来进行打包:
pyinstaller -w -F --add-data assets;assets my_app_name.py
打包完成后会生成一个包含嵌入资源的单独的exe,无需将资源文件放到同一文件夹下也能正常运行。
–add-data的参数由源文件名src和目标文件名dest组成。路径的源文件名和目标文件名用文件分隔符进行分隔,源文件名是该文件或文件夹的原本的路径,目标文件名是该文件夹嵌入到exe后的放入的文件夹名。
文件分隔符:在Windows系统上是分号,大部分unix系统上是冒号,可以通过os.pathsep来查看当前系统上的文件分隔符。例如:
>>> import os >>> os.pathsep ';'
- 1
- 2
- 3
比如–add-data "assets;assets"就表示将原本assets里面的所有文件,放入打包后的assets文件夹。再比如–add-data "assets/*.mp3;music"表示将原本assets里面的所有mp3文件,放入打包后的music文件夹。
打包完成后,默认的程序图标是一个“蛇”形,但我们也可以进行更改。(根据官方文档,该功能只能在Windows和macOS上使用)
–icon或-i参数用于设置图标,该参数的值默认为"NONE",表示使用默认的图标;也可以指定为一个*.ico格式的Windows图标文件路径;*.icns的Mac图标文件路径;或者一个其他图片文件(需安装pillow模块,会通过pillow模块将其转换成标准的ico/icns格式)。
首先添加一个图标文件。图标文件在Windows上格式为*.ico,Mac上是*.icns。
- my_app
- assets
- image.gif
- my_app_name.py
- icon.ico
这个图标文件其实放在哪里都可以,因为打包完成后其实它也相当于嵌入了exe。但为了方便,还是把它放到同一文件夹下比较好。
pyinstaller -i icon.ico my_app_name.py
为了方便看,之前设置的-w, -F这些选项都省略了。最后生成了一个图标与icon.ico相一致的exe。
pyinstaller单文件模式启动速度较慢,所以可能需要一个启动画面(闪屏)进行过渡,提示用户正在进行加载。这个启动画面可以是单张图片,也可以是文本(默认情况下文本禁用,使用方式参见第5章)。
这个启动画面的实现基于Tcl/Tk(和python tkinter模块一样),打包时会附带约1.5MB的额外文件来支持这个功能。
支持闪屏,需要先准备一张图片,必须是PNG格式(如果你安装了pillow模块,可以用pillow模块支持的其他格式)。然后,在打包时加上–splash参数,并传入图片路径。
pyinstaller --splash splash.png my_app_name.py
控制闪屏可以通过pyi_splash模块,这个模块和上一节的sys._MEIPASS属性一样,在没有通过pyinstaller打包成exe后是不起作用的,所以必须带上try…except…代码。
pyi_splash.close()方法用于关闭闪屏。一般放在程序开头即可,因为只要运行到程序开头,说明pyinstaller的加载就基本完成了。
于是,在程序开头部分添加以下代码:
try:
import pyi_splash
pyi_splash.close()
except ImportError:
pass
如果不用这段代码进行关闭,那么闪屏将一直显示。
打包后,闪屏效果如下。
至于pyi_splash还有一个update_text方法,用于在闪屏画面上显示加载文本,将在5.7节介绍。
–disable-windowed-traceback参数用于禁用异常提示。如果不添加这个参数,将会在非控制台程序出错(似乎仅限非致命的错误)时弹出一个窗口报告异常信息(注意:仅在隐藏控制台模式下弹出异常报告窗口)。为了测试,我在代码第一行添加了raise Exception,运行打包后的exe后效果如图所示。
当你调用以上的打包方式时,会在脚本的文件夹下生成一个*.spec文件。
*.spec文件包含了打包需要使用的所有配置信息。直接在命令行中将*.spec文件路径传给pyinstaller,也可以进行打包。比如:
pyinstaller my_app_name.spec
(其中my_app_name.spec是根据my_app_name.py生成的Spec文件)
这样,当你多次打包同一个项目时,就无需每次都传入那么多参数,只需要传入*.spec文件的路径即可。
*.spec文件也比较好处理,直接使用python编辑器或记事本就能编辑。
使用pyi-makespec工具可以根据pyinstaller的命令行参数生成Spec文件。用法很简单,在原先使用pyinstaller的打包命令中,把"pyinstaller"换成"pyi-makespec"就可以生成一个Spec文件。例如:
pyi-makespec -w -F --add-data assets;assets my_app_name.py
要更改Spec文件的生成路径,可以指定参数–specpath。
如果报错提示找不到pyi-makespec,转到最后一章:常见问题。
当你使用*.spec文件进行pyinstaller打包时,大部分的打包参数都不可用,需要预先在*.spec文件中预先设定。
pyinstaller会将*.spec里面的内容当做代码执行。单文件模式和文件夹模式的*.spec文件略有不同。
下面是一个*.spec文件(单文件模式打包)的例子。
# -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis( ['my_app_name.py'], pathex=[], binaries=[], datas=[('assets', 'assets')], 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) exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='my_app_name', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=False, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, )
下面是一个文件夹模式的*.spec文件的例子:
# -*- mode: python ; coding: utf-8 -*- block_cipher = None a = Analysis( ['my_app_name.py'], pathex=[], binaries=[], datas=[('assets', 'assets')], 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) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name='my_app_name', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=False, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, ) coll = COLLECT( exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name='my_app_name', )
这里面包含一些特殊的类,比如Analysis, PYZ, EXE等,文件夹模式下还多了一个COLLECT类。只有当pyinstaller运行时才会被定义,很显然你不能在python解释器中直接调用它们。 这些类的参数与pyinstaller的命令行参数并不一样。
接下来将针对Spec文件中的这些对象进行介绍
Analysis类包含一些分析信息,它分析模块的导入以及一些依赖文件。
这个类的常用参数介绍如下。
参数名 | 默认值 | 描述 | (常用参数)示例 |
scripts | 必选参数,无默认值 | 需要分析的文件路径列表(一般就是需要打包的文件) | [“myscript.py”] |
pathex | None | 需要额外进行分析模块导入的文件(夹)路径,包含命令行–path参数指定内容 | [“C:/Python310/Lib/site-packages”, "C:/my_module] |
binaries | None | 需要嵌入的二进制文件列表,包含命令行–add-binary参数指定内容 | |
datas | None | 需要嵌入的非二进制文件(夹),包含命令行–add-data参数指定内容 | [(“assets”, “assets”), (“music/*.mp3”, “music”)] |
hiddenimport | None | 需要额外导入的模块列表 | [“module1”, “module2”] |
hookspath | None | 钩子文件路径列表(钩子文件用于配置一些模块特殊的导入,后文详解) | |
hooksconfig | None | 一个字典,包含钩子的配置信息 | |
excludes | None | 需要被忽略,不进行导入的模块列表 | |
runtime_hooks | None | 运行时的钩子列表,指定为一系列文件名 | |
noarchive | False | 如果设为True,则不会将源代码放到一个存档中进行存储,而是作为多个单独的文件 |
在完成分析后,需要将一些属性传递给PYZ类。Analysis对象包含了以下属性,你可以不必了解它们:
属性名 | 描述 |
scripts | 同参数中的scripts |
pure | 需要一起打包的纯python模块 |
pathex | 同参数中的pathex |
binaries | 同参数中的binaries |
datas | 同参数中的datas |
完成分析后,将Analysis对象的一些属性传递给PYZ类。PYZ相当于一个压缩包,里面储存了所有的依赖文件。
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
定义PYZ对象后,接下来需要定义EXE对象,也就是可执行文件对象。
不同打包模式(单文件或文件夹)的EXE对象参数略有不同。其中常用参数如下:
参数 | 默认值 | 描述 | (常用参数)示例 |
console | True | 是否显示控制台,相当于命令行-w参数 | |
disable_windowed_traceback | False | 是否禁用异常提示,相当于命令行–disable-windowed-traceback参数 | |
name | None | 可执行文件的名称。在Windows上会自动添加".exe"后缀 | “my_app_name” |
icon | None | 可执行文件的图标路径 | “icon.ico” |
使用文件夹模式打包时还会有一个COLLECT对象,该对象用于创建文件夹。它有一个常用的关键字参数name,表示文件夹的名称。
如果你要在macOS上创建应用程序,且你的应用程序是无控制台的,那么在exe构建完成之后还需要添加一些代码。
app = BUNDLE(exe,
name='my_app_name.app',
icon="icon.ico",
bundle_identifier=None)
如果你想要在应用中添加启动画面(图片和文本都可以),需要在Spec文件中额外添加一个Splash对象进行控制。
在分析完代码后,创建Splash对象:
a = Analysis(...)
splash = Splash('splash.png',
binaries=a.binaries,
datas=a.datas,
text_pos=(10, 50),
text_size=12,
text_color='black')
然后在EXE中绑定splash对象。注意:单文件模式和文件夹模式方式略有不同。
以下是单文件模式绑定splash对象的方法。
splash = Splash(...)
exe = EXE(pyz,
a.scripts,
splash, # <-- both, splash target
splash.binaries, # <-- and splash binaries
...)
以下是文件夹模式的方法。
splash = Splash(...)
exe = EXE(pyz,
splash, # <-- splash target
a.scripts,
...)
coll = COLLECT(exe,
splash.binaries, # <-- splash binaries
...)
下面介绍Splash对象的一些参数。注意:由于Splash窗口基于Tcl/Tk(和python tkinter一样),所以里面有一些用法与Tcl/Tk(tkinter)的用法很像,但不重要。
参数 | 默认值 | 描述 | (常用参数)示例 |
image_file | 必选参数,无默认值 | 图片文件路径,必须是PNG格式(如果你安装了pillow模块,可以用pillow模块支持的其他格式) | “splash.png” |
binaries | 必选参数,无默认值 | Analysis对象的binaries属性 | |
datas | 必选参数,无默认值 | Analysis对象的datas属性 | |
text_pos | None | 闪屏文本相对于闪屏图片的显示位置(是一个(x, y)元组,锚点为文本左下角)。如果不指定,则禁用文本显示 | (500, 400) |
text_size | 12 | 文本大小 | |
text_font | “TkDefaultFont” | 文本使用的字体(必须是系统上安装了的字体),如果不指定则设为系统默认字体 | “宋体” |
text_color | “black” | 文本颜色,颜色格式可以是颜色名称字符串或者十六进制颜色字符串,如"#ff00ff"(注意:不支持(r, g, b)元组形式) | |
text_default | “Initializing” | 默认显示的文本(后面可以用pyi_splash.update_text来更新显示的文本) | “加载中……” |
max_img_size | (760, 480) | 最大闪屏图片尺寸。如果超出尺寸,那么闪屏图片将会被按纵横比缩放,容纳到该尺寸中。可以设为None不缩放 | |
always_on_top | True | 闪屏窗口是否置顶,如果置顶,其位于其他窗口之上 | |
rundir | “__splash” | 设置运行闪屏时,用于存放一些相关文件的文件夹名称。使用这个参数主要是为了避免命名冲突,一般不会使用 |
下面就以一个示例来演示Splash的文本显示。使用的代码还是上一章节使用的。
在开头添加以下代码:
try:
import pyi_splash
import time
for i in range(100):
text = f"加载中……进度{i}%"
time.sleep(0.1) # 模拟一个速度比较慢的加载过程
pyi_splash.update_text(text) # 更新显示的文本
pyi_splash.close() # 关闭闪屏
except ImportError:
pass
然后通过pyi-makespec生成对应的Spec文件:
pyi-makespec -w -F --add-data assets;assets --splash splash.png my_app_name.py
由于Splash的文本显示只能在Spec文件中进行配置,所以我们先打开my_app_name.spec,将Splash对象的代码进行修改,如下所示:
splash = Splash(
'splash.png',
binaries=a.binaries,
datas=a.datas,
text_pos=(30, 270),
text_size=12,
minify_script=True,
always_on_top=True,
)
然后进行打包:
pyinstaller my_app_name.spec
运行效果如下:
可以看到,首先显示文本被设定为加载的各个依赖文件,然后变成update_text中自己设定的加载内容。
有些产品由几个不同的应用程序组成,每个应用程序可能依赖于一组通用的第三方库,或者以其他方式共享一部分代码。在打包这样的产品时,如果单独对待每个应用程序,将其与所有依赖项捆绑在一起,那就太可惜了,因为这意味着要存储代码和库的副本。
此时,我们可以使用多包特性来捆绑一组可执行应用程序,以便它们共享库的单个副本。我们可以在单文件或单文件夹应用程序中做到这一点。
比如有两个应用都使用了tkinter模块,且这两个应用相关,需要在发布时放到一起(比如一个应用专门用于图片剪裁,另外一个专门用于图片滤镜,它们可能共用了部分功能)。如果分别打包,那么每个应用都会包含一个tkinter模块的依赖文件,而且都储存相同的内容,这就很浪费存储空间。如果用多包捆绑的话,只会有一个tkinter模块的依赖文件,两个应用都可以调用相同的依赖。
如果采用文件夹模式,想要捆绑多个应用程序,那么只需要共享一个COLLECT对象。假如有hello1.py, hello2.py,将这两个应用进行捆绑,可以将它们的Spec文件进行一些组合。
首先通过pyi-makespec分别生成hello1.py, hello2.py的Spec文件。
然后将其中的Analysis, PYZ, EXE, Splash等对象分别以不同的变量名放入同一个Spec文件,然后将它们的COLLECT对象组合起来。
hello1_a = Analysis(['hello1.py'], ...) hello1_pyz = PYZ(hello1_a.pure, hello1_a.zipped_data, ...) hello1_exe = EXE(hello1_pyz, hello1_a.scripts, ...) hello2_a = Analysis(['hello2.py'], ...) hello2_pyz = PYZ(hello2_a.pure, hello2_a.zipped_data, ...) hello2_exe = EXE(hello2_pyz, hello2_a.scripts, ...) coll = COLLECT(hello1_exe, hello1_a.binaries, hello1_a.zipfiles, hello1_a.datas, hello2_exe, hello2_a.binaries, hello2_a.zipfiles, hello2_a.datas, ... name='hello')
这样,将会生成同一个文件夹,该文件夹下包含两个文件hello1.exe, hello2.exe。 它们共享一部分的依赖文件。
单文件模式下,多包捆绑会生成多个单独的exe,其中一个exe包含它们共有的依赖文件。
比如打包hello1.py和hello2.py,设置hello1包含共有的依赖文件,最后生成hello1.exe, hello2.exe。生成的hello1.exe由于包含两个exe共有的依赖文件,其文件大小会大于hello2.exe。
运行hello1.exe时与单独打包效果相同。但是运行hello2.exe时,它会在hello1.exe中搜索它需要的依赖文件,速度会稍慢。
如果将hello2.exe移动到别的地方,或者将hello1.exe改名,那么hello2.exe将无法运行,因为它找不到hello1.exe,从而无法找到所需的依赖文件。
以下是hello1.py和hello2.py两个程序文件,将以它们为例进行打包。
# hello1.py
while True:
input("hello1")
# hello2.py
while True:
input("hello2")
首先通过pyi-makespec生成对应的Spec文件。完成后,将两个Spec文件的Analysis类汇总到一个文件中,并进行改名。
a1 = Analysis(
['hello1.py'],
...
)
a2 = Analysis(
['hello2.py'],
...
)
接下来在下面调用MERGE函数。这个函数会分析两个文件中重复的依赖项,将结果放到分析类的dependencies属性中。MERGE中位于第一个的程序将会包含共有的依赖项。
MERGE((a1, "hello1", "hello1"), (a2, "hello2", "hello2"))
然后将两个文件的ZIP和EXE进行汇总。汇总时需要额外向EXE类传递一个参数Analysis.dependencies。
pyz1 = PYZ(...) exe1 = EXE(pyz1, a1.dependencies, #### a1.scripts, a1.binaries, a1.zipfiles, a1.datas, ...) pyz2 = PYZ(...) exe2 = EXE( pyz2, a2.dependencies, #### a2.scripts, a2.binaries, a2.zipfiles, a2.datas, ...)
保存文件,然后通过pyinstaller打包。
最后生成两个文件,可以看到hello1.exe的文件大小比hello2.exe大了很多,这是由于hello1.exe中包含了它们共有的依赖库。如果不使用多包捆绑,而是分别单独进行打包,那么两个文件的大小将都会超过5000KB。
有一些特殊的模块,它们存在一些特殊的依赖文件(比如ico, json等等)。而pyinstaller的导入分析无法检测到这些特殊的依赖文件,这就导致运行后出现问题。于是,pyinstaller引入了“钩子”。钩子文件其实就是一种python文件,后缀名为*.py即可(和Spec文件的实质是一样的)。钩子文件中指定了某个特殊模块所需要的所有依赖文件。通过传递钩子文件,pyinstaller就能找到那些“隐藏”的依赖文件。
虽然钩子文件的作用也可以被–hiddenimport, --datas这些命令行参数替代,但是使用钩子显然更加方便。
pyinstaller有一些内置的“钩子”,提供了一些常用模块的钩子文件,它们包含Django, pickle, pyqt, scipy等等。
钩子文件的常用命名格式是:hook-module.py(其中module是模块名)。(当然你也可以按自己喜好命名)
钩子文件中可以包含以下全局变量(有一些变量可以不被写在文件中):
属性 | 描述 | (常用属性)示例 |
hiddenimports | 需要额外导入的模块列表,相当于命令行–hidden-import参数 | [“sys”, “pygame.mixer”] |
excludedimports | 需要被排除,不被自动导入的模块列表(如果有一些模块在其他地方被导入,那么仍然会导入它) | [“tkinter”] |
datas | 需要备添加的非二进制文件或文件夹,相当于命令行–add-data参数 | [(‘/usr/share/icons/education_*.png’, ‘assets’) ] |
binaries | 需要备添加的二进制文件,相当于命令行–add-binary参数 |
一、Python所有方向的学习路线
Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。
二、Python必备开发工具
工具都帮大家整理好了,安装就可直接上手!
三、最新Python学习笔记
当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。
四、Python视频合集
观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
五、实战案例
纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
六、面试宝典
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。