flask——python 的前后端分离

2024 年 9 月 30 日 星期一(已编辑)
/
156
1

阅读此文章之前,你可能需要首先阅读以下的文章才能更好的理解上下文。

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 强大的各种库以及简单无脑的特性。轻量、模块化、专一,追求优雅的工程师真不是好工程师?

示例项目

由于没有任何前端基础,只是简单制作了一个足以起步的项目示例。

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...