flask——python 的前后端分离
为什么要前后端分离
我事实上并没有完成过任何一个前后端分离的项目,作为 C 语言选手,过去写的所有小工具都是以大对象传参进行上下层的调用,并且设计过一种虚假的前后端分离项目框架,以便于合作开发。
后来朋友推荐 redis 数据库,结合 PYQT 做桌面应用也可以前后端分离,只是完全依靠键值对通信的话,键值表未免有些庞大,键命名非常头疼。而且还是没有脱离做C 语言项目的思路,本质上只是将全局状态结构体弄成了 redis 数据库以使得前后端项目可以独立运行,依然无法彻彻底底模块化。
搭建博客还有一些小网站的过程中,感觉 web 开发确实挺好的,尤其是让我从C 语言的思路中脱离出来,随地拉屎、急速开发。于是找到了 python 的 web 开发方式,flask+socket+webview+process,大概可以多进程并发了吧。
说到底为什么执着于前后端分离,还是为了进一步把软件模块化,以期做成更大规模的软件。
如何使用
在原作者老哥的库下,有 example 例子,把 app.py 和 templates 下的 .html 粘到自己的 demo 项目中就可以用起来了。缺啥库装啥库。
进阶的用法
原作者老哥的例子是没有使用 webview 和 process 的,为了完成一个桌面应用,能多进程跑前后台,能有自己的应用窗口是必备的。
结合这两者有如下的示例,增改在原有的库例上
import multiprocessing
from multiprocessing import Process
import webview
import os
import signal
from engineio.async_drivers import eventlet
async_mode = "eventlet" # 显式指明异步库
......
def run_flask():
socketio.run(app, host='127.0.0.1', port=5000) # 指定主机和端口
# socketio.run(app)
def run_webview():
webview.create_window('pyweb测试', 'http://127.0.0.1:5000') # 创建浏览器窗口
webview.start() # 启动 pywebview
if __name__ == '__main__':
multiprocessing.freeze_support()
flask_process = Process(target=run_flask)
flask_process.start()
webview_process = Process(target=run_webview)
webview_process.start()
webview_process.join()
os.kill(flask_process.pid, signal.SIGTERM)
其中这两句是为了未来的打包,打包成exe 后不显式指明 async_mode 是无法使用的
from engineio.async_drivers import eventlet
async_mode = "eventlet" # 显式指明异步库
这一个部分是为了将原有的示例 socketio 服务器 以及 webview 窗口抽离成两个独立的进程。
def run_flask():
socketio.run(app, host='127.0.0.1', port=5000) # 指定主机和端口
# socketio.run(app)
def run_webview():
webview.create_window('pyweb测试', 'http://127.0.0.1:5000') # 创建浏览器窗口
webview.start() # 启动 pywebview
if __name__ == '__main__':
multiprocessing.freeze_support()
flask_process = Process(target=run_flask)
flask_process.start()
webview_process = Process(target=run_webview)
webview_process.start()
webview_process.join()
os.kill(flask_process.pid, signal.SIGTERM)
特别需要关注的是 multiprocessing.freeze_support()
这一句,由于 Process 库在 windows 的特性,没有这一句打包后会疯狂开进程,导致电脑直接卡爆,必须关机重启。Recipe Multiprocessing · pyinstaller/pyinstaller Wiki (github.com)
关于打包
结合此前进阶用法的示例,在代码中关于打包的坑基本避免了。但是我们是前后端分离的程序,项目结构的复杂度与此前的程序完全不能比,所以必须使用 .spec 精细化配置了。有这样的示例。
# myapp.spec
# -*- mode: python -*-
block_cipher = None
# Analysis部分用于分析应用程序的依赖
a = Analysis(['main.py'], # 应用程序的主脚本
pathex=['.'], # 应用程序路径
binaries=[], # 需要包含的二进制文件
datas=[('templates', 'templates')], # 包含 HTML 模板
hiddenimports=['flask', 'flask_socketio', 'eventlet', 'serial',
# 这部分是必须手动指明的
'engineio.async_eventlet','eventlet.hubs.epolls','eventlet.hubs.kqueue','eventlet.hubs.selects','dns','dns.dnssec','dns.e164','dns.edns','dns.entropy','dns.exception','dns.flags','dns.grange','dns.hash','dns.inet','dns.ipv4','dns.ipv6','dns.message','dns.name','dns.namedict','dns.versioned','dns.node','dns.opcode','dns.query','dns.rcode','dns.rdata','dns.rdataclass','dns.rdataset','dns.rdatatype','dns.renderer','dns.resolver','dns.reversename','dns.rrset','dns.set','dns.tokenizer','dns.tsig','dns.tsigkeyring','dns.ttl','dns.update','dns.version','dns.wiredata','dns.zone','av','fvcore','torch','torchvision','detectron2',
], # 隐式导入的模块
hookspath=[], # 自定义hook路径
hooksconfig={}, # hooks配置
runtime_hooks=[], # 运行时hook
excludes=[], # 排除的模块
win_no_prefer_redirects=False, # Windows下是否优先使用重定向
win_private_assemblies=False, # 是否使用私有程序集
cipher=block_cipher, # 数据加密
noarchive=False) # 是否不打包成zip文件
# PYZ部分用于打包Python字节码
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
# EXE部分用于定义可执行文件的设置
exe = EXE(pyz,
a.scripts, # 指定需打包的脚本
[],
exclude_binaries=True, # 不打包二进制文件
name='myapp', # 应用程序名称
debug=False, # 是否启用调试模式
bootloader_ignore_signals=False, # 处理信号
strip=True, # 是否去除调试信息
upx=True, # 是否使用UPX压缩
console=False) # 是否使用控制台窗口
# COLLECT部分用于收集所有依赖项
coll = COLLECT(exe,
a.binaries, # 收集的二进制文件
a.zipfiles, # 收集的zip文件
a.datas, # 收集的数据文件
strip=False, # 是否去除调试信息
upx=True, # 是否使用UPX压缩
upx_exclude=[], # 不压缩的文件
name='myapp') # 最终应用程序名称
其中有一大坨 'engineio.async_eventlet'......这些东西,是打包后打开 exe 会报缺失的包,事实上就是报什么包缺失,就在这里加上就好,这一大坨也是从别人的指令打包那里借鉴过来,测试了几次打包,确实会挨个报缺失,索性全部弄进去了。
最后使用如下指令打包,“my.spec” 是自定义的 .spec 文件名
pyinstaller my.spec
最终打包出的 exe 文件运行后会有一个 .WebView2 文件夹的展开,不必理会,总大小大概在 30Mb 以内,比起 qt pyside 这些大的不像话的包已经强很多了。
后记
执着于这件事与厌恶 QT 也有关系吧,PYQT 和 pyside 的限制还是太大了,完全是基地车展开,包含了大量用不上的功能,且只能用框架内的功能,总是与其他包产生冲突,完全浪费了 python 强大的各种库以及简单无脑的特性。轻量、模块化、专一,追求优雅的工程师真不是好工程师?
示例项目
由于没有任何前端基础,只是简单制作了一个足以起步的项目示例。