一、项目需要。
- 将Flask项目打包成Linux可执行【不至于源码太过于暴露在外】
- 方便其他需要,不在赘述。
二、项目环境[主要库版本]。
- Flask==0.10.1
- Flask-SocketIO==4.3.2
- eventlet==0.30.0
- torch==1.5.1-CPU
- torchvision==0.2.2.post3【特别注意】
- …
三、踏坑记录
a、尝试直接编译文件
- 项目启动文件
main.py
;直接尝试pyinstaller -F main.py
,漫长的等待后【3分钟左右】会在main.py
目录下生成dist
和build
文件夹和另外一个类似于CMakeList
的文件main.spec[注意:后面需要修改]
,直接运行dist
目录下的可执行文件main
,报错:
参照:https://github.com/miguelgrinberg/python-socketio/issues/35确定可行的解决方案:
具体操作如下:
在代码socketIO实例化的地方指定 -
socketio = SocketIO(app, async_mode="eventlet")
; - 在
main.spec
的hiddenimports
选项中添加['engineio.async_drivers.eventlet']
; - 在启动文件文件最上面添加两条导包语句,这些导包项目运行用不到,给pyinstaller使用:
from eventlet.hubs import epolls, kqueue, selects
from dns import dnssec, e164, hash, namedict, tsigkeyring, update, version, zone
- 注意:此时不可以直接运行
pyinstaller main.py
因为重新生成的main.spec
覆盖掉旧的;
b、添加静态文件
在main.spec
的datas
选项中配置:
参照另一位博主的方法【参见利用pyinstaller打包Python项目包含多个文件夹】:
进行我的配置:
说明如下:
(1) py文件打包配置
针对多目录多文件的python项目,打包时候需要将所有相关的py文件输入到Analysis类里。Analysis类中的pathex定义了打包的主目录,对于在此目录下的py文件可以只写文件名不写路径。如上的spec脚本,将所有项目中的py文件路径以列表形式写入Analysis,这里为了说明混合使用了绝对路径和相对路径。
(2) 资源文件打包配置
资源文件包括打包的python项目使用的相关文件,如图标文件,文本文件等。对于此类资源文件的打包需要设置Analysis的datas
比如:datas=[(SETUP_DIR + ‘\db\input‘,‘db\input‘),(SETUP_DIR + ‘\doc‘,‘doc‘)],只需要添加依赖的输入目录以及放图片、模板的目录即可。
注意:SETUP_DIR + ‘\db\input‘为源码中的绝对路径,Windows路径需要用\来转义,后面的db\input为打包后生成的目录,写相对路径即可。这样我们就不需要再手动把依赖的文件拷贝到run目录了
(3)Hidden import配置
pyinstaller在进行打包时,会解析打包的python文件,自动寻找py源文件的依赖模块。但是pyinstaller解析模块时可能会遗漏某些模块(not visible to the analysis phase),造成打包后执行程序时出现类似No Module named xxx。这时我们就需要在Analysis下hiddenimports中加入遗漏的模块。
如:
from core.barcode_handler import *
from conf import settings as ss
from core.log import Log as log
只需要写入自定义的模块即可
hiddenimports=[‘core‘,‘core.main‘,‘conf.settings‘,‘core.log‘,‘core.barcode_handler‘]
c、其他需要注意问题:
-
torchvision
以及torchvision 版本
导致Error:torch OSError: could not get source code
:
在.spec
脚本中添加excluded_modules = ['torch.distributions']
如果由于运行过程中还有其他问题导致该错误,需要将torchvision
降级【参照【已解决】OSERROR: COULD NOT GET SOURCE CODE的问题 ,WINDOWS下使用PYINSTALLER打包遇到此问题】pip uninstall torchvision
pip install torchvision==0.2.2.post3
至此,利用pyinstaller 以目录方式打包pyinstaller -D main.spec
已经可以正常运行;但,尝试将项目打包为单文件时提示找不到静态文件;
解决打包单文件找不到静态文件的问题:
- 将静态文件添加进
spec
脚本的datas
中后,运行依然提示找不到静态文件:
重点:单文件模式下,运行可执行文件时,程序会先将可执行文件进行压缩,压缩的位置在 /temp目录 下,再执行,所以被打包进去的数据文件在被解压的路径下,而,程序是在运行的路径下搜索,即可执行文件的目录下,所以找不到数据文件 参见博文PyInstaller 带静态依赖文件打包教程 核心代码:
# 先获取当前运行时临时目录路径
if getattr(sys, 'frozen', None):
basedir = sys._MEIPASS
else:
basedir = path.dirname(__file__)
# 使用 os.path.join() 方法,将 临时目录路径与文件相对路径拼接
with open(path.join(basedir, 'file.txt'), 'r') as fp:
pass
到这里依然无法找到打包进去的静态文件:
划重点:
即使利用 sys._MEIPASS
拿到了/tmp/_MEI*
路径,但还不够,需要切换工作目录【参见pyinstaller打包找不到文件的问题解决】:
import os, sys
def base_path():
if getattr(sys, 'frozen', None):
basedir = sys._MEIPASS
else:
basedir = os.getcwd()
return basedir
# cwd = os.getcwd() # 这是程序的所在路径
wdir = base_path()
# 当需要调用打包的外部文件时
os.chdir(wdir) # 先把工作路径变成解压路径
do() # 执行你要干的事情
# 当需要写出文件到程序所在目录时
os.chdir(cwd) # 把工作路径切换回来
do() # 执行你要干的事情
意不意外,惊不惊喜~
补充:这种打包方案容易反编译,下一步准备尝试py文件 → c文件 → pyd文件
进行编译,教程贴已备好【参见PyInstaller将Python文件打包为exe后如何反编译(破解源码)以及防止反编译】