Pyinstaller
Pyinstaller可以用来打包python代码,生成可执行文件(主流平台都可以),介绍就不说了,可以百度或者去官网看看:https://www.pyinstaller.org/
以Windows为例,简单说一下主要过程:
安装pyinstaller:
pip install pyinstaller
打包,为了讲述方便,贴一张项目的目录结构,方便理解:
main.py可以理解成是整个项目的接口模块,也是pyinstaller要进行打包的模块。main.py里边的内容很简单,通常就是调用mainInSrc.py里的模块执行。而整个项目的真正逻辑都是从mainInSrc.py里开始的。
打包的指令也很简单,进到项目文件夹,然后执行(请注意这么做完之后,运行通常都会报错):
pyinstaller main.py
指令执行完后,可以看见文件夹里多了些东西:
执行前:
执行后:
进入dist文件夹,里边会看见一个main文件夹,这个文件夹就是生成的打包文件,里边会有一个main.exe可执行文件。通常情况下,这个main.exe很难执行成功,因为发布的是带界面的程序,里边会用到一些图片或者本地数据。就是即使项目里文件路径写的再好,都可能出现运行程序找不到文件导致程序启动失败。解决这个问题需要了解以下几个问题:
- 打包模式是什么?
- 运行路径是什么?
- 如何为程序添加本地文件或数据?
解决找不到文件无法启动的问题
1. 打包模式是什么?
一般情况,可能用到的打包主要有两种,一种是打包成单独文件夹模式(One Folder),一种是单一文件模式(One File)。默认是One Folder模式。
默认情况下,pyinstaller的打包是One Folder模式:
pyinstaller main.py
如果想打包成One File模式:
pyinstaller -F main.py
2. 运行路径是什么?
可以明确的说,运行打包完的执行文件和从源码运行程序路径肯定是不同的。当捆绑应用程序启动时,引导加载程序设置sys.frozen,并将绑定文件夹的绝对路径存储在sys._MEIPASS中。对于一个文件夹包,这是该文件夹的路径。对于单文件包,这是引导加载程序创建的临时文件夹的路径。
所以,为了避免反复修改文件路径,首先要做的是首先分辨出程序到底是从可执行文件运行还是从源码运行,具体通过如下代码判断:
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
print('running in a PyInstaller bundle')
else:
print('running in a normal Python process')
有了这个判断代码,可以将文件路径分别设置在各自条件下,这样至少可以保证程序在运行时都是在你期望的路径下去寻找文件。
假设你的源文件运行是没有问题的,接下来解决可执行文件文件路径问题:
这里要清楚的是One Folder模式和One File模式在执行时路径是不同的。当然和从源码运行也是不同的。这个可以使用__file__来获取:
- 从源码运行:__file__是当前模块路径
- One Folder:__file__是打包文件夹的路径
- One File: __file__是临时文件夹的路径
所以,结合sys._MEIPASS,建立从打包文件运行的绝对路径,通过如下代码实现:
from os import path
import sys
bundle_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))+"\\images\\"
file_path = path.abspath(path.join(bundle_dir, file_name))
这里假设我的图片文件夹就在打包文件下。我从图片文件夹中获取文件。也就是main.exe和images文件夹同级。以此类推,你可以随意设置你想要的路径。
设置为从打包文件获取文件,接下来设置从源代码运行获取文件,你可以直接把你之前源代码中获取文件的路径复制过来,也可以仿照上述代码,构成获取文件路径代码,还是以本例子中的结构为例,代码如下:
from os import path
import sys
bundle_dir = getattr(sys, '_MEIPASS', path.abspath(path.dirname(__file__))) + "\\..\\view\\images\\"
file_path = path.abspath(path.join(bundle_dir, file_name))
这么写的好处是,实际代码中,可以将
getattr(sys, '_MEIPASS', path.abspath(path.dirname(__file__)))
提取出来包装成一个函数,用来获取程序运行时根目录的绝对路径。可以很方便的供更多函数使用。
最终,获取文件路径的完整代码如下:
def getFilePath(file_name):
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
bundle_dir = getattr(sys, '_MEIPASS', path.abspath(path.dirname(__file__)))+"\\images\\"
file_path = path.abspath(path.join(bundle_dir, file_name))
else:
bundle_dir = getattr(sys, '_MEIPASS', path.abspath(path.dirname(__file__))) + "\\..\\view\\images\\"
file_path = path.abspath(path.join(bundle_dir, file_name))
return file_path
3.如何为程序添加本地文件或数据?
设置完路径,需要考虑如何添加本地文件,添加本地文件有两种方法,一种是直接在打包过程中,通过–add-data实现;第二种通过配置.spec文件实现。这里推荐第二种,第一种可以去官网看看:链接
打包执行结束,会在main.py同级别生成一个main.spec文件,这个文件可以编辑并且可以被pyinstaller执行。
使用编辑器打开main.spec文件,在a = Analysis()中找到datas,将所需要添加的文件添加到里边,这里比较推荐的一种做法是先创建一个变量,然后将所要添加的内容一次性写好,然后将变量传给datas:
added_files = [
( 'src/view/images', 'images' ),
( 'src/templates/template.html', 'templates' ),
('output', 'output')
]
a = Analysis(['emailAGS.py'],
pathex=['C:\\Users\\90621\\Documents\\我的项目\\emailAutoGeneration'],
binaries=[],
datas=added_files,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
保存后,运行:
pyinstaller main.spec
这个语句实际上就是一个更新的过程,你不需要重新打包main.py,只需要运行main.spec。即使在你更改了你的源代码后,你依然可以用main.spec进行更新。
至此,因文件找不到而无法运行的问题顺利解决。
需要注意的是,打包成One File的好处是方便并且因为每次运行是在临时文件夹中,所以程序可以同时运行并不影响。缺点是临时文件夹不会自己消失,所以时间长会占用空间。打包成One Folder的好处是不会有临时文件产生,但是不能同时运行多个。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)