风过空庭,字句正徐来。
关于关于本站关于我给我点钱
更多时间线友链文件服务wiki
联系写留言发邮件GitHub
© 2024-2026 yono. | RSS 订阅 | 站点地图 | | Stay hungry. Stay foolish.
Powered by Mix Space&
白い
.
| 粤 ICP 备2024284785号-1 |
正在被0人看爆
纸白微明,未成篇章。

flask——python 的前后端分离

(已编辑)
/
215
1
AI·GEN

关键洞察

这篇文章上次修改于,可能部分内容已经不适用,如有疑问可询问作者。

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

  • Streamlit desktop 项目的起步

flask——python 的前后端分离

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • 为什么要前后端分离

    我事实上并没有完成过任何一个前后端分离的项目,作为 C 语言选手,过去写的所有小工具都是以大对象传参进行上下层的调用,并且设计过一种虚假的前后端分离项目框架,以便于合作开发。

    后来朋友推荐 redis 数据库,结合 PYQT 做桌面应用也可以前后端分离,只是完全依靠键值对通信的话,键值表未免有些庞大,键命名非常头疼。而且还是没有脱离做C 语言项目的思路,本质上只是将全局状态结构体弄成了 redis 数据库以使得前后端项目可以独立运行,依然无法彻彻底底模块化。

    搭建博客还有一些小网站的过程中,感觉 web 开发确实挺好的,尤其是让我从C 语言的思路中脱离出来,随地拉屎、急速开发。于是找到了 python 的 web 开发方式,flask+socket+webview+process,大概可以多进程并发了吧。

    说到底为什么执着于前后端分离,还是为了进一步把软件模块化,以期做成更大规模的软件。

    如何使用

    在原作者老哥的库下,有 example 例子,把 app.py 和 templates 下的 .html 粘到自己的 demo 项目中就可以用起来了。缺啥库装啥库。

    进阶的用法

    原作者老哥的例子是没有使用 webview 和 process 的,为了完成一个桌面应用,能多进程跑前后台,能有自己的应用窗口是必备的。

    结合这两者有如下的示例,增改在原有的库例上

    PYTHON
    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 是无法使用的

    PYTHON
    from engineio.async_drivers import eventlet
    async_mode = "eventlet" # 显式指明异步库
    

    这一个部分是为了将原有的示例 socketio 服务器 以及 webview 窗口抽离成两个独立的进程。

    CodeBlock Loading...

    特别需要关注的是 multiprocessing.freeze_support() 这一句,由于 Process 库在 windows 的特性,没有这一句打包后会疯狂开进程,导致电脑直接卡爆,必须关机重启。Recipe Multiprocessing · pyinstaller/pyinstaller Wiki (github.com)

    关于打包

    结合此前进阶用法的示例,在代码中关于打包的坑基本避免了。但是我们是前后端分离的程序,项目结构的复杂度与此前的程序完全不能比,所以必须使用 .spec 精细化配置了。有这样的示例。

    CodeBlock Loading...

    其中有一大坨 'engineio.async_eventlet'......这些东西,是打包后打开 exe 会报缺失的包,事实上就是报什么包缺失,就在这里加上就好,这一大坨也是从别人的指令打包那里借鉴过来,测试了几次打包,确实会挨个报缺失,索性全部弄进去了。

    最后使用如下指令打包,“my.spec” 是自定义的 .spec 文件名

    CodeBlock Loading...

    最终打包出的 exe 文件运行后会有一个 .WebView2 文件夹的展开,不必理会,总大小大概在 30Mb 以内,比起 qt pyside 这些大的不像话的包已经强很多了。

    后记

    执着于这件事与厌恶 QT 也有关系吧,PYQT 和 pyside 的限制还是太大了,完全是基地车展开,包含了大量用不上的功能,且只能用框架内的功能,总是与其他包产生冲突,完全浪费了 python 强大的各种库以及简单无脑的特性。轻量、模块化、专一,追求优雅的工程师真不是好工程师?

    示例项目

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

    NanBar/webvuepy: python关于web的探索demo,vue框架作为前端

    PYTHON
    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)
    
    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')  # 最终应用程序名称
    
    
    pyinstaller my.spec