目录
问题描述
Python项目打包后,找不到配置文件路径
1.冻结路径
2.使用方法
将资源文件打包到exe文件中
1.打包前准备
2.返回临时路径
3.使用方法
4.编译打包
问题描述
pyinstaller 打包py文件成exe文件,执行打包后的程序,经常会出现程序使用的配置文件无法关联,或者,在打包后的路径下运行正常,但是将打包后的程序放到其它路径下就有问题。
这些现象都很有可能是因为程序使用的文件路径发生改变产生的,因此在打包时候我们需要根据执行路径进行路径“冻结”。
Python项目打包后,找不到配置文件路径
1.冻结路径
如何‘冻结’路径呢?
创建一个frozen.py的文件
import os
import sys
def app_path():
if hasattr(sys, 'frozen'):
return os.path.dirname(sys.executable) #使用pyinstaller打包后的exe目录
return os.path.dirname(__file__) #没打包前的py目录
hasattr()内置函数:
英文文档:
hasattr(object, name)
The arguments are an object and a string. The result is True if the string is the name of one of the object’s attributes, False if not. (This is implemented by calling getattr(object, name) and seeing whether it raises an AttributeError or not.)
- 说明: 函数功能用来检测对象object中是否含有名为name的属性,如果有则返回True,如果没有返回False
其中的app_path()函数返回一个程序的执行路径,为了方便我们将此文件放在项目文件的根目录,通过这种方式建立了相对路径的关系。
源代码中使用路径时,以app_path()的返回值作为基准路径,其它路径都是其相对路径。
2.使用方法
例如:
在需要使用路径的py文件中,import frozen
import frozen
# Windows
if os.name == 'nt':
config_path = frozen.app_path() + r'\config.conf' #配置文件路径
# linux
else:
config_path = frozen.app_path() + r'/config.conf' #配置文件路径
将资源文件打包到exe文件中
1.打包前准备
先使用pyinstaller命令对主程序进行打包:
如果没有pyinstaller命令,先进行下载:pip install pyinstaller
打包的两种方式:
1. pyinstaller -D test.py # 生成一个文件夹,里面是多文件模式,启动快。
2. pyinstaller -F test.py # 仅仅生成一个文件,不暴露其他信息,启动较慢。
先执行一次pyinstaller -F test.py ,不管使用那种方式都会生成一个以.spec结尾的文件,文件里的内容如下:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['om_agent.py'],
pathex=['venv/lib/python3.7/site-packages/', '/home/OM_Socket_agent'],
binaries=[],
datas=[('ca','ca')],
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='om_agent',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True )
这其实就是一个python文件,只不过后缀是spec罢了。
.spec一共会有4个对象,分别是:Analysis、PYZ、EXE、COLLECT。
Analysis用处最多,一个个解释:
- 第一个参数,是指定我们整个项目的主程序,也就是我们的入口文件。
- pathex,就是我们的工作目录,第一个引号中是打包时所用到的依赖包(提前通过pip install -r requirements.txt 命令下载(linux环境)),第二个引号中是当前的工作目录
- datas,存放我们的数据。例:datas=[('ca','ca')],方括号中存放的是一个个的元祖 ,如果有多个资源目录需要编写多个,datas=[('ca','ca'),('config','config'),],
exe中的name=‘om_agent’,是打包后exe文件的名称。
当我们运行编译好的exe后,他是会创建一个临时目录,把所有需要用的包都解压到那里,然后执行。执行完毕后,临时文件夹就消失了。
如果你的项目中需要去读取某些文件,甚至是用户的输入参数,怎么办?打包出来的exe 是没有办法通过直接指定参数,类似:python main.py --input=*.xlsx 来读取文件的,因为上面说了在执行的时候会把项目解压到一个临时目录,所以原来项目中写好的相对路径也不管用。
为此,我们需要把实际文件给copy到那个临时目录下,所以这个datas的作用就是这个,我的文件中,我把/home//OM_Socket_agent/ca/文件都copy到临时目录/home//OM_Socket_agent/dist/ca/的下面。
2.返回临时路径
那刚刚说的临时目录在代码里怎么处理呢,如果代码中还是老样子处理相对路径,一定是找不到的。
官方文档中给出了这么一段:
Your app should run in a bundle exactly as it does when run from source. However, you may need to learn at run-time whether the app is running from source, or is “frozen” (bundled).
import sys
if getattr( sys, 'frozen', False ) :
# running in a bundle
basedir = sys._MEIPASS
else :
# running live
所以在你的项目中,如果有配置文件的话,就在那里加上这一段,然后在bundle中添加你的新路径,else还是你的老代码。
这个 sys._MEIPASS 是个特殊的值,是在Pyinstaller打包的时候才会添加的临时变量,通过这个变量我们可以获取到在执行exe时候的临时目录。
在frozen.py中添加如下代码:
#生成资源文件目录访问路径
def resource_path(relative_path):
if hasattr(sys, 'frozen'): #是否Bundle Resource
base_path = sys._MEIPASS #返回临时路径
else:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
基本原理:Pyinstaller 可以将资源文件一起bundle到exe中,当exe在运行时,会生成一个临时文件夹,程序可通过sys._MEIPASS访问临时文件夹中的资源。
3.使用方法
原代码(伪代码):
import ssl
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations('ca/master.crt')
修改代码(伪代码):
import ssl
import frozen,os
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
path = frozen.resource_path(os.path.join('ca','master.crt'))
context.load_verify_locations(path)
修改代码后,代码在pycharm等平台无法运行成功,因为读取路径方法发生变化,没关系,我们要的是打包exe后能运行就可以。
4.编译打包
最后,我们再执行 python -F xxx.spec 就好了。打包的可执行文件会在dist里,build中是一些打包时候需要的文件。