高性能的Python应用程序
发布时间:2025-06-24 17:02:11 作者:北方职教升学中心 阅读量:721
通过这个项目,你将掌握如何利用FastAPI和现代前端技术,快速构建现代化、
实时通信:
- 通过WebSocket实现前后端的实时通信,适合需要实时更新的应用场景。
- 前端使用现代前端技术:无论是React、 """logger.debug(f"echo_handler 被调用,消息为: {message}")try:# 向客户端发送回显消息awaitwebsocket.send_text(f"回显: {message}")exceptException ase:# 捕获并记录任何异常logger.error(f"echo_handler 中发生错误: {e}")@register_command('custom')asyncdefcustom_handler(message,websocket):""" 处理 'custom' 命令的异步函数。
- 后端使用FastAPI,代码简洁,易于维护。JavaScript)构建美观的用户界面。 """uvicorn.run(self.app,host=self.host,port=self.port)# 使用uvicorn运行绑定的FastAPI应用实例,# 监听指定的host和portif__name__ =="__main__":# 创建参数解析器parser =argparse.ArgumentParser(description='启动WebSocket服务器和Webview窗口')parser.add_argument('--server',action='store_true',help='仅启动服务器')parser.add_argument('--host',default='0.0.0.0',help='服务器监听地址')parser.add_argument('--port',type=int,default=8000,help='服务器监听端口')parser.add_argument('--index-path',default=os.path.join("templates","index.html"),help='网页入口文件路径')# 解析命令行参数args =parser.parse_args()# 检查 index 文件是否存在ifnotos.path.exists(args.index_path):logger.error(f"文件 {args.index_path}不存在,无法启动服务器。传统的GUI库如 PySide6、
static/
:存放静态文件的目录。D3.js等可视化库,都可以无缝集成。最大化、 参数: message (str): 收到的消息内容。Angular等前端框架,还是Tailwind CSS、 参数: websocket (WebSocket): WebSocket连接对象。高性能的Python应用程序。Vue、Bootstrap等CSS框架,甚至是Three.js、
现代化UI:
- 利用现代前端技术(如Flexbox、
参考文档:
- FastAPI官方文档
- Jinja2官方文档
版权声明:本文为CSDN博主的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。以下是核心代码:
2.1 导入必要的库
fromfastapi importAPIRouter,Requestfromfastapi.templating importJinja2Templates# 创建一个APIRouter实例,用于定义API的路由router =APIRouter()# 初始化Jinja2Templates实例,指定模板文件的目录templates =Jinja2Templates(directory='templates')
2.2 定义HTTP路由
我们定义了三个HTTP路由,用于渲染HTML页面:
# 定义根路径的GET请求处理函数# 返回index.html模板,同时传入一个空的request对象@router.get("/")asyncdefget():# 使用TemplateResponse方法渲染index.html模板,并传递一个空的request对象returntemplates.TemplateResponse("index.html",{"request":{}})# 定义/test路径的GET请求处理函数# 在控制台打印"test",并返回"test"作为HTTP响应@router.get("/test")asyncdeftest():# 在控制台中打印 "test"print("test")# 返回 "test" 作为响应return"test"# 定义/info路径的GET请求处理函数# 接收Request对象作为参数,用于获取请求相关数据@router.get("/info")asyncdefinfo(request:Request):# 下面是用于演示的变量,包含不同类型的参数custom_param ="这是一个自定义参数"# 自定义字符串参数number_param =42# 整数参数list_param =["item1","item2","item3"]# 列表参数dict_param ={"key1":"value1","key2":"value2"}# 字典参数# 构造静态文件的URL,这里假设你有一个 style.css 文件在 static 目录下static_file_url =request.url_for('static',path='style.css')# 返回info.html模板,传入request对象和多个参数returntemplates.TemplateResponse("info.html",{"request":request,# 传递request对象,用于模板渲染"custom_param":custom_param,# 传递自定义字符串参数"number_param":number_param,# 传递整数参数"list_param":list_param,# 传递列表参数"dict_param":dict_param,# 传递字典参数"static_file_url":static_file_url # 传递静态文件的URL})
3. APP路由注册中转
在
app.py
中,我们使用自动注册函数发现并注册所有路由地址,并挂载静态资源:# 导入必要的模块fromimportlib importimport_module # 动态导入模块frompathlib importPath # 操作文件路径frompkgutil importiter_modules # 遍历包中的模块fromfastapi importFastAPI,APIRouter # 创建FastAPI应用和API路由fromfastapi.staticfiles importStaticFiles # 提供静态文件服务fromloguru importlogger # 记录日志# 创建FastAPI应用实例app =FastAPI()# 挂载静态文件目录,使得可以通过/static/访问静态资源app.mount("/static",StaticFiles(directory="static"),name="static")# 定义一个字典用于缓存已导入的模块,避免重复导入_imported_modules ={}defregister_routers(package_name='routers'):""" 自动注册指定包下的所有API路由。Vue、 """webview.create_window('Web Socket Example',str(self.index_path),width=1000,height=800,resizable=True)# 创建一个名为'Web Socket Example'的窗口,加载index_path指定的HTML文件,# 设置窗口宽度为1000px,高度为800px,并允许调整大小webview.start(self.start_server,gui='cef')# 启动webview事件循环,保持窗口打开,并在启动时调用start_server方法defstart_server(self):""" 启动FastAPI服务器。
依赖浏览器引擎:
- WebView依赖于系统或内置的浏览器引擎,可能存在兼容性问题。
app.py
:路由发现及注册器。- 支持与后端的双向通信(通过HTTP API或WebSocket)。而 Dear PyGui和 PySimpleGUI等新兴库虽然简化了开发流程,但在复杂应用场景中仍存在一定的局限性。
架构畅想
1. 架构图
+-------------------+ +-------------------+ +-------------------+| | | | | || FastAPI 后端 |<----->| WebView 前端 |<----->| 现代前端技术 || | | | | |+-------------------+ +-------------------+ +-------------------+
2. 组件说明
FastAPI 后端:
- 提供RESTful API和WebSocket支持。 参数: package_name (str): 包含API路由的包名,默认为'routers' """# 获取当前文件所在目录,并拼接上包名得到包的实际路径package_dir =Path(__file__).resolve().parent /package_name # 记录正在注册路由的日志信息logger.info(f"正在注册路由,包目录: {package_dir}")try:# 遍历包中的所有模块for(_,module_name,_)initer_modules([str(package_dir)]):# 如果模块已经导入过,则直接使用缓存中的模块ifmodule_name in_imported_modules:module =_imported_modules[module_name]else:# 否则动态导入模块并缓存module =import_module(f"{package_name}.{module_name}")_imported_modules[module_name]=module # 记录成功导入模块的日志信息logger.debug(f"导入模块: {module_name}")# 尝试从模块中获取名为'router'的对象router =getattr(module,'router',None)# 如果获取到的对象是APIRouter实例,则将其注册到FastAPI应用中ifisinstance(router,APIRouter):app.include_router(router)logger.debug(f"已注册路由: {module_name}")else:# 如果未找到有效的APIRouter实例,记录警告日志logger.warning(f"模块 {module_name}没有找到有效的 APIRouter 实例")exceptException ase:# 如果发生任何异常,记录错误日志logger.error(f"注册路由时发生错误: {e}")# 调用函数注册所有路由register_routers()
4. 主程序入口
在
main.py
中,我们整合了所有路由并启动FastAPI应用:# 导入必要的库和模块importos # 提供与操作系统交互的功能,如文件路径操作importthreading # 允许程序并发运行多个线程,用于同时启动服务器和webview窗口importtime # 提供时间处理函数,如休眠importargparse # 解析命令行参数importuvicorn # 用于运行FastAPI应用的ASGI服务器实现importwebview # 创建原生桌面GUI窗口,用于显示HTML页面fromloguru importlogger # 强大的日志记录库,提供简洁的日志输出fromapp importapp # 导入FastAPI应用实例classWebSocketServer:def__init__(self,**kwargs):""" 初始化WebSocketServer类的实例。macOS和Linux上运行。
- 通过
uvicorn
运行,支持高并发和实时通信。 - 跨平台工具:需要在多个操作系统上运行的工具类应用。关闭)。Tkinter、WebView和现代前端技术构建一个完整的Python应用程序,涵盖了从后端API设计到前端页面开发的完整流程。
如果你有任何问题或建议,欢迎在评论区留言,或者直接在我的GitHub项目 fastapi-blog-tutorial 或者gitee项目 fastapi-blog-tutorial中提交Issue。
- 快速原型开发:需要快速构建和迭代的应用场景。 参数: kwargs (dict): 包含配置项的字典,支持以下键值: - host (str): 服务器监听地址,默认为 '0.0.0.0' - port (int): 服务器监听端口,默认为 8000 - index_path (str): 网页入口文件路径,默认为 "templates/index.html" """self.host =kwargs.get('host','0.0.0.0')# 设置服务器监听地址self.port =kwargs.get('port',8000)# 设置服务器监听端口self.index_path =kwargs.get('index_path',os.path.join("templates","index.html"))# 设置网页入口文件路径self.app =app # 绑定FastAPI应用实例defstart_webview(self):""" 启动webview窗口,加载指定的HTML文件。
总结
通过本文,你已经学会了如何使用FastAPI构建一个支持WebSocket和模板渲染的完整Web应用。
templates/
:存放Jinja2模板文件的目录。
- 提供RESTful API和WebSocket支持。 参数: package_name (str): 包含API路由的包名,默认为'routers' """# 获取当前文件所在目录,并拼接上包名得到包的实际路径package_dir =Path(__file__).resolve().parent /package_name # 记录正在注册路由的日志信息logger.info(f"正在注册路由,包目录: {package_dir}")try:# 遍历包中的所有模块for(_,module_name,_)initer_modules([str(package_dir)]):# 如果模块已经导入过,则直接使用缓存中的模块ifmodule_name in_imported_modules:module =_imported_modules[module_name]else:# 否则动态导入模块并缓存module =import_module(f"{package_name}.{module_name}")_imported_modules[module_name]=module # 记录成功导入模块的日志信息logger.debug(f"导入模块: {module_name}")# 尝试从模块中获取名为'router'的对象router =getattr(module,'router',None)# 如果获取到的对象是APIRouter实例,则将其注册到FastAPI应用中ifisinstance(router,APIRouter):app.include_router(router)logger.debug(f"已注册路由: {module_name}")else:# 如果未找到有效的APIRouter实例,记录警告日志logger.warning(f"模块 {module_name}没有找到有效的 APIRouter 实例")exceptException ase:# 如果发生任何异常,记录错误日志logger.error(f"注册路由时发生错误: {e}")# 调用函数注册所有路由register_routers()
打包体积较大:
- 由于需要嵌入浏览器引擎,打包后的应用程序体积较大。
- 使用现代前端技术(如HTML、
--index-path <路径>
: 指定网页入口文件的路径,默认为templates/index.html
。 websocket (WebSocket): WebSocket连接对象。样式定制困难,且难以适应现代前端技术的快速发展。--host <地址>
: 指定 FastAPI 服务器监听的地址,默认为0.0.0.0
。- 实时数据展示:如监控系统、
例如,如果你想启动服务器并指定监听地址和端口,可以运行:
python app/main.py --server--host127.0.0.1 --port8080
如果你想启动桌面应用并指定网页入口文件路径,可以运行:
python app/main.py --index-path templates/custom_index.html
4. 访问主页
打开浏览器,访问 http://127.0.0.1:8000,你应该能够看到主页。
- 使用FastAPI构建RESTful API和WebSocket服务。
--port <端口>
: 指定 FastAPI 服务器监听的端口,默认为8000
。WebView 前端:
- 使用
pywebview
库将前端页面嵌入到桌面应用中。这种方案的核心思想是:
- 后端使用FastAPI:提供高性能的API和WebSocket支持。Vue.js 或 Tailwind CSS),构建现代化的 Python 应用程序 GUI。以下是核心代码:
1.1 HTML结构
<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>WebSocket 客户端 v1.1</title><linkrel="stylesheet"href="/static/style.css"></head><body><h1>WebSocket 客户端 v1.1</h1><divid="messages"></div><divclass="button-container"><buttonclass="button"onclick="sendMessage('echo','Hello Server!')">💬 发送 Echo 命令</button><buttonclass="button"onclick="sendMessage('time','Get Time')">🕒 发送 Time 命令</button><buttonclass="button"onclick="clearMessages()">🗑️ 清除消息</button><buttonclass="button"onclick="reconnect()">🔄 重新连接</button><buttonclass="button"onclick="openSettings()">⚙️ 设置</button></div><divclass="input-container"><inputtype="text"id="messageInput"placeholder="输入消息..."><buttonclass="button"onclick="sendMessage('custom',document.getElementById('messageInput').value.trim())">📤 发送</button></div><divid="connectionStatus"class="connection-status"><divclass="status-icon"></div><span>连接状态:未连接</span></div><divid="toast"class="toast"></div><divclass="overlay"id="overlay"></div><divclass="settings-panel"id="settingsPanel"><h2>设置</h2><labelfor="serverPort">端口号</label><inputtype="text"id="serverPort"placeholder="例如: 8000"><labelfor="serverPath">路径</label><inputtype="text"id="serverPath"placeholder="例如: /ws"><buttononclick="saveSettings()">保存</button></div><scriptsrc="/static/script.js"></script></body></html>
1.2 CSS样式
:root{--primary-color:#6C7A89;--secondary-color:#5C6A79;--background-gradient:linear-gradient(135deg,#f5f7fa,#c3cfe2);--box-shadow:0 4px 12px rgba(0,0,0,0.1);--transition:all 0.3s ease;--text-color:#2C3E50;--white:rgba(255,255,255,0.95);--success-color:#27ae60;--error-color:#E74C3C;}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;display:flex;flex-direction:column;align-items:center;margin:0;padding:20px;background:var(--background-gradient);color:var(--text-color);height:100vh;overflow:hidden;}h1{margin-bottom:20px;font-size:clamp(2rem,5vw,2.5rem);color:#34495e;text-shadow:2px 2px 4px rgba(0,0,0,0.1);animation:fadeIn 0.8s ease-in-out;}.button-container{display:flex;justify-content:center;gap:10px;margin:20px 0;flex-wrap:wrap;animation:fadeIn 0.8s ease-in-out;}.button{background:linear-gradient(135deg,var(--primary-color),var(--secondary-color));color:#fff;border:none;padding:12px 24px;cursor:pointer;border-radius:30px;font-size:14px;display:flex;align-items:center;gap:8px;transition:var(--transition);box-shadow:var(--box-shadow);}.button:hover{transform:translateY(-2px);box-shadow:0 6px 12px rgba(0,0,0,0.2);opacity:0.9;}#messages{margin:20px 0;padding:20px;width:90%;max-width:800px;height:300px;overflow-y:scroll;background:var(--white);border-radius:15px;box-shadow:var(--box-shadow);backdrop-filter:blur(10px);}.message{padding:12px;border-radius:15px;margin-bottom:15px;max-width:80%;word-wrap:break-word;background:var(--white);box-shadow:var(--box-shadow);display:flex;justify-content:space-between;align-items:center;opacity:0;animation:fadeIn 0.5s forwards ease-in-out;transition:var(--transition);will-change:transform,opacity;}.message-client{background:linear-gradient(135deg,#e3f2fd,#bbdefb);margin-left:auto;border-radius:15px 15px 0 15px;}.message-server{background:linear-gradient(135deg,#f0f4f8,#d9e2ec);margin-right:auto;border-radius:15px 15px 15px 0;color:#2d3748;}.input-container{display:flex;justify-content:center;align-items:center;width:90%;max-width:800px;margin-top:20px;gap:10px;}input[type="text"]{flex:1;padding:12px;border:1px solid rgba(0,0,0,0.1);border-radius:30px;outline:none;background:var(--white);font-size:14px;color:inherit;transition:var(--transition);}input[type="text"]:focus{border-color:var(--primary-color);box-shadow:0 0 12px rgba(108,122,137,0.3);background:rgba(255,255,255,1);}.connection-status{margin-top:10px;font-size:14px;color:inherit;display:flex;animation:fadeIn 0.8s ease-in-out;align-items:center;gap:8px;}.connection-status.connected{color:var(--success-color);}.connection-status.disconnected{color:var(--error-color);}.toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:var(--primary-color);color:#fff;padding:16px 32px;border-radius:50px;box-shadow:var(--box-shadow);opacity:0;transform:translate(-50%,20px);transition:var(--transition);font-size:16px;z-index:1000;}.toast.show{opacity:1;transform:translate(-50%,0);}.settings-panel{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--white);padding:30px;border-radius:15px;box-shadow:var(--box-shadow);backdrop-filter:blur(10px);z-index:1001;display:none;width:90%;max-width:400px;text-align:center;}.settings-panel.show{display:block;}.overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:1000;display:none;}.overlay.show{display:block;}
1.3 JavaScript逻辑
letws;constconnectionStatus =document.getElementById('connectionStatus');consttoast =document.getElementById('toast');constoverlay =document.getElementById('overlay');constsettingsPanel =document.getElementById('settingsPanel');constserverPortInput =document.getElementById('serverPort');constserverPathInput =document.getElementById('serverPath');functionshowToast(message,duration =2000){toast.textContent =message;toast.classList.add('show');setTimeout(()=>toast.classList.remove('show'),duration);}functionopenSettings(){overlay.classList.add('show');settingsPanel.classList.add('show');constsavedPort =localStorage.getItem('serverPort')||'8000';constsavedPath =localStorage.getItem('serverPath')||'/ws';serverPortInput.value =savedPort;serverPathInput.value =savedPath;}functionsaveSettings(){constport =serverPortInput.value.trim();constpath =serverPathInput.value.trim();localStorage.setItem('serverPort',port);localStorage.setItem('serverPath',path);overlay.classList.remove('show');settingsPanel.classList.remove('show');reconnect();}functioninitWebSocket(){constport =localStorage.getItem('serverPort')||'8000';constpath =localStorage.getItem('serverPath')||'/ws';ws =newWebSocket(`ws://localhost:${port}${path}`);ws.onopen=()=>{addMessage('已连接到服务器','server');connectionStatus.innerHTML =`<div class="status-icon"></div><span>连接状态:已连接</span>`;connectionStatus.classList.add('connected');connectionStatus.classList.remove('disconnected');showToast('已连接到服务器');};ws.onmessage=e=>addMessage(`服务器消息: ${e.data}`,'server');ws.onclose=()=>{addMessage('已断开与服务器的连接','server');connectionStatus.innerHTML =`<div class="status-icon"></div><span>连接状态:已断开</span>`;connectionStatus.classList.add('disconnected');connectionStatus.classList.remove('connected');showToast('已断开与服务器的连接');};ws.onerror=e=>{addMessage(`WebSocket 错误: ${e.message}`,'server');connectionStatus.innerHTML =`<div class="status-icon"></div><span>连接状态:错误</span>`;connectionStatus.classList.add('disconnected');showToast('WebSocket 错误: '+e.message);};}functionsendMessage(command,data){if(ws &&ws.readyState ===WebSocket.OPEN){ws.send(JSON.stringify({command,data}));addMessage(`客户端消息: ${data}`,'client');document.getElementById('messageInput').value ='';showToast('消息已发送');}else{addMessage('WebSocket 连接未打开,请重新连接','server');showToast('WebSocket 连接未打开,请重新连接');}}functionclearMessages(){document.getElementById('messages').innerHTML ='';showToast('消息已清除');}functionreconnect(){clearMessages();connectionStatus.innerHTML =`<div class="loading"></div><span>连接状态:重新连接中...</span>`;initWebSocket();showToast('正在重新连接...');}functionaddMessage(message,type){constmessagesDiv =document.getElementById('messages');constmessageDiv =document.createElement('div');messageDiv.classList.add('message',`message-${type}`);messageDiv.innerHTML =`<p>${message}</p><time datetime="${newDate().toISOString()}">${newDate().toLocaleTimeString()}</time>`;messagesDiv.appendChild(messageDiv);messagesDiv.scrollTop =messagesDiv.scrollHeight;}document.getElementById('messageInput').addEventListener('keypress',e=>{if(e.key ==='Enter')sendMessage('custom',document.getElementById('messageInput').value.trim());});initWebSocket();
程序展示
1.运行项目
- 克隆我的示例项目:
gitclone https://github.com/Ktovoz/fastapi-blog-tutorial.gitcdfastapi-blog-tutorial
2. 安装依赖
确保你已经安装了所有必要的依赖项,可以通过运行以下命令来完成:
pip install-rrequirements.txt
3. 启动应用
3.1 启动 FastAPI 服务器
在终端中运行以下命令来启动 FastAPI服务器:
python main.py --server
3.2 启动桌面应用
在终端中运行以下命令来启动 PyWebview桌面应用:
python main.py
3.3 参数说明
你可以通过以下参数来配置应用的启动行为:
--server
: 仅启动 FastAPI 服务器,不启动 PyWebview 桌面应用。 websocket (WebSocket): WebSocket连接对象。 """logger.debug(f"time_handler 被调用,消息为: {message}")try:# 获取当前时间并格式化为ISO8601字符串now =datetime.datetime.now().isoformat()# 向客户端发送服务器时间awaitwebsocket.send_text(f"服务器时间是 {now}")exceptException ase:# 捕获并记录任何异常logger.error(f"time_handler 中发生错误: {e}")1.4 定义WebSocket路由
我们定义了一个WebSocket路由,处理客户端连接和消息:
@router.websocket("/ws")asyncdefwebsocket_endpoint(websocket:WebSocket):""" WebSocket端点的主处理函数。以下是核心代码:
1.1 导入必要的库
# 导入必要的库和模块importdatetimeimportjsonfromfastapi importAPIRouter,WebSocket,WebSocketDisconnectfromloguru importlogger# 创建一个API路由实例,用于定义WebSocket端点router =APIRouter()# 定义一个字典来存储命令及其对应的处理函数command_handlers ={}
1.2 注册命令处理器
我们定义了一个装饰器
register_command
,用于注册命令处理器:defregister_command(command):""" 注册命令处理器的装饰器函数。高性能的Python应用程序。
- 实现前后端的实时通信和数据交互。这个项目展示了如何使用FastAPI、
缺点
性能开销:
- WebView和前端渲染需要一定的系统资源,可能不适合对性能要求极高的场景。
- 后端使用FastAPI:提供高性能的API和WebSocket支持。Vue.js 或 Tailwind CSS),构建现代化的 Python 应用程序 GUI。以下是核心代码:
优缺点分析
优点
开发效率高:
- 前端使用现代前端技术,开发速度快,样式定制灵活。CSS和JavaScript实现了一个美观的WebSocket客户端页面。CSS、
通过这种方案,开发者可以摆脱传统GUI库的束缚,充分利用现代前端技术的优势,快速构建现代化、 参数: message (str): 收到的消息内容(本例中未使用)。 参数: message (str): 收到的消息内容。 """logger.debug(f"custom_handler 被调用,消息为: {message}")try:# 向客户端发送自定义回显消息awaitwebsocket.send_text(f"自定义回显: {message}")exceptException ase:# 捕获并记录任何异常logger.error(f"custom_handler 中发生错误: {e}")@register_command('time')asyncdeftime_handler(message,websocket):""" 处理 'time' 命令的异步函数。
- 利用Tailwind CSS、
- 使用
可扩展性强:
- 前后端分离,便于团队协作和功能扩展。
- 桌面应用:需要现代化UI的Python桌面应用程序。Bootstrap等CSS框架实现快速样式开发。
为了解决这一问题,我们提出了一种全新的解决方案:使用 FastAPI 作为后端服务,结合 PyWebView 和现代前端技术(如 React、无论你是Python开发者,还是前端开发者,都可以从中获得启发和实用的技巧。如果你正在为Python GUI开发而烦恼,不妨试试这种全新的解决方案!
环境准备
在开始之前,确保你已经安装了以下Python库:
fastapi
uvicorn
loguru
jinja2
你可以通过以下命令安装这些依赖:
pip installfastapi uvicorn loguru jinja2
项目结构
以下是项目的目录结构:
.├── fastapi-blog-tutorial/ # 应用代码目录│ ├── main.py # 主入口文件│ ├── routers/ # 路由定义│ ├── templates/ # 模板文件│ └── models/ # 数据模型(如果使用数据库)├── tests/ # 测试代码目录├── requirements.txt # 项目依赖文件└── README.md # 项目介绍文件
main.py
:主程序入口,包含FastAPI应用实例。此外,像 Kivy这样的库虽然支持跨平台和丰富的交互效果,但其学习曲线较陡,且对现代Web技术的支持有限。- 通过WebSocket与后端实时交互,实现动态数据更新。
- 使用WebView将前端页面嵌入到Python应用中。CSS Grid、实时数据仪表盘。
希望这篇博文对你的项目有所帮助!如果有其他需求,欢迎随时提出!
为了帮助开发者更好地理解和实践这一方案,我创建了一个示例项目:fastapi-blog-tutorial。
告别传统GUI:用FastAPI + PyWebView + 现代前端技术打造Python应用界面
引言
在Python应用程序开发中,GUI(图形用户界面)的实现一直是一个痛点。wxPython等虽然功能强大,但开发复杂、如果你访问不了github,可以使用gitee链接 fastapi-blog-tutorial
接下来的教学将基于此项目,逐步讲解如何实现以下功能:
适用场景
代码解析
1. WebSocket命令处理
我们实现了一个WebSocket服务器,支持多种命令处理。
routers/
:存放所有路由模块的目录。学习曲线:
- 需要掌握现代前端技术,对纯Python开发者可能有一定学习成本。
现代前端技术:
- 使用React、
- 处理业务逻辑、 参数: command (str): 要注册的命令名称。这种方法非常适合需要实时通信和动态页面生成的项目。动画效果)实现美观的用户界面。 返回: decorator (function): 实际的装饰器函数,用于包装命令处理函数。
- 集成Three.js、 """defdecorator(func):# 记录正在注册的命令及其处理函数名logger.debug(f"正在注册命令 '{command}',处理函数为 '{func.__name__}'")command_handlers[command]=func # 记录当前所有已注册的命令处理器logger.info(f"当前注册的命令处理器: {', '.join(f'{cmd}: {handler.__name__}'forcmd,handler incommand_handlers.items())}")returnfunc # 返回原始函数,不改变其行为returndecorator
1.3 定义命令处理器
我们定义了三个示例命令处理器:
@register_command('echo')asyncdefecho_handler(message,websocket):""" 处理 'echo' 命令的异步函数。数据存储和通信。这种架构不仅能够充分利用现代前端技术的灵活性和强大功能,还能通过 FastAPI 提供高效的后端支持,实现前后端分离的开发模式,从而显著提升开发效率和用户体验。
- 提供原生的窗口管理功能(如最小化、 websocket (WebSocket): WebSocket连接对象。 """awaitwebsocket.accept()# 接受WebSocket连接logger.info("正在处理连接")try:whileTrue:# 接收来自客户端的消息data =awaitwebsocket.receive_text()# 尝试将接收到的数据解析为JSON格式try:request =json.loads(data)exceptjson.JSONDecodeError:logger.error(f"无法解码JSON消息: {data}")awaitwebsocket.send_text("无效的JSON格式")continue# 提取命令和数据command =request.get('command')data =request.get('data','')# 检查命令是否合法ifcommand notincommand_handlers:logger.warning(f"非法命令: {command}")awaitwebsocket.send_text("非法命令")continue# 记录解析出的命令和数据logger.debug(f"解析命令: {command}, 数据: {data}")# 查找并调用相应的命令处理函数ifcommand incommand_handlers:logger.debug(f"找到命令 '{command}',调用处理函数 '{command_handlers[command].__name__}'")awaitcommand_handlers[command](data,websocket)else:logger.warning(f"未知命令: {command}")awaitwebsocket.send_text(f"未知命令: {command}")exceptWebSocketDisconnect:# 处理客户端断开连接的情况logger.info("客户端已断开连接")exceptException ase:# 捕获并记录其他任何异常logger.error(f"处理消息时发生错误: {data}, 错误: {e}")
2. 模板渲染
我们使用Jinja2模板引擎渲染动态HTML页面。
跨平台支持:
- WebView和现代前端技术天然支持跨平台,可以在Windows、")else:logger.info(f"服务器已启动,地址为 http://localhost:{args.port}和 ws://localhost:{args.port}/ws")# 实例化WebSocketServer类server =WebSocketServer(host=args.host,port=args.port,index_path=args.index_path)ifargs.server:# 仅启动服务器server.start_server()else:# 启动webview窗口server.start_webview()
网页部分
1. 前端页面设计
我们使用HTML、
- 通过WebView嵌入前端页面:将前端页面直接嵌入到桌面应用中,实现无缝的GUI体验。Angular等前端框架构建用户界面。D3.js等可视化库,实现复杂的数据可视化。