遇到相关问题欢迎邮件交流(1554610593@qq.com),或评论区留言(不过回应会很慢)。
单兵简单开发,Python几乎可以满足所有需求。那么,2024年了,想写一个简单的windows桌面端软件,点击exe打开运行的那种,并且所有后端业务都用Python编写,应该怎么做?请看这个笔者用Python开发的用于微信群聊拍卖统价的桌面端软件:
效果是很好的,既美观又好用,并且所有的业务代码都是用Python写的(诸如管理数据库、读取和写入excel表格、计算竞价)。
用Python编写桌面GUI,可能最先想到的就是pyQt、Tkinter这种,但它们不是太臃肿,就是学习曲线高,而且功能局限,开发出的前端并不美观。
在开发前端方面,没有比Web网页开发更灵活强大的了,何况还有现成的web框架和海量的组件可以直接使用。但是,网页毕竟要在浏览器中打开,并不算一个封装好的桌面端软件。使用Electron倒是可以把网页浏览器伪装成一个桌面软件,但抛开学习成本不说,软件的业务层(也就是后端)是用node.js编写的,远没有Python灵活强大。
那么,2024年了,能不能用Web网页做前端,Python写后端,然后封装成一个桌面软件的样子呢?这样一来,只需要会Python和一点前端js框架,就可以写出一个美观又功能强大的桌面软件了。这就是笔者想推荐一种技术方案:pywebview (框架) + Python (后端) + vue (前端) + pyinstaller (打包)。目前也已经有大佬想到了这一套方案,并开发出了一个框架 PPX,方便大家直接在此基础上开发。笔者用这套方案很方便地开发出了桌面端软件。
pywebview是一个Python库,可以用pip直接安装,几行Python代码就能把一个网页伪装成桌面窗口打开。原理是调用了系统的浏览器内核,打开了一个浏览器窗口来渲染网页。最神奇的是,它允许前端js中直接调用后端用Python编写的函数。具体的实现方式是在Python中编写一个api类,业务代码写成它的成员函数xxx,pywebview在创建窗口时接收这个api类和前端网页作为参数,然后你就可以在前端js代码中直接用pywebview.api.xxx去调用Python函数。vue是最流行的前端Web框架之一,它的优点是上手简单,template和原生html很像,js的编写简单(尤其是新版本的vue支持组合式API写法),同时还有Element Plus等配套的UI组件库可以使用。Python实现后端业务有诸多好处,最重要的是Python有丰富的库可以直接调用,功能非常强大。Python也有Django等前后端开发框架,但是这种传统的web开发模式的前后端是通过网址api的方式通信的。而在pywebview下,前端可以直接调用后端Python函数,前后端的通信pywebview都帮你封装好了,开发者可以专注于用Python进行业务功能实现。项目写好后,将vue项目打包成一个小巧的文件夹(包含html和js等),pywebview中的前端指向该文件夹就可以将其渲染出来。由于其它部分都是用Python编写的,可以用pyinstaller将其打包成一个exe文件。这套方案在框架 PPX 中已经帮你跑通了,感兴趣的读者可以去仔细研究一下这个框架的源码,相信也能自己用Python捣鼓出一个桌面端软件。但其中肯定也会遇到一些坑,诸如笔者遇到过的这些:
项目打包后,窗口打开白屏。在开发阶段vue网页都打开正常,但最后用vite生成了html和js,并打包好了exe文件,打开就白屏了。这个时候最好在pywebview的参数里设置保留控制台,这样打开窗口后可以按F12查看控制台(没错,就是chrome浏览器的那个控制台),看看是什么报错。如果也没什么报错,可能就是vite打包的问题,笔者就遇到了vite引起的和pywebview不对付的情况。所以还是推荐用之前的Vue CLI脚手架来搭建和生成vue项目吧,vite确实有一些坑。打包时间太长,生成的exe过大。pyinstaller打包也很有讲究,如果不是在新建的Python环境中打包,很可能把很多无关的Python库也打包进来。建议感兴趣的读者去研究一下 PPX 的作者是怎么处理的,不懂的也可以评论区留言。关于在js中调用pywebview的Python函数,还有一些讲究的: pywebview会自动将js的数据类型和Python的数据类型进行转换。具体的,函数返回的Python对象都将被jsonify,就是转换成json格式发给前端,所以只有list、dict等这类基础类型可以在前后端之间传递。其它类型要想办法转换成可以变成json的再传递。在js调用后端api函数 (比如xxx),最好封装一下,处理好错误信息:try { pywebview.api.xxx(params).then((res) => { // ...处理后端返回的结果res... console.log('成功执行'); }).catch((err) => { // 如果Python执行抛出异常,会在这里处理 console.log(err.message) })} catch (err) {// 如果js函数执行出错(如不存在xxx函数,或pywebview还没加载好)// 会在这里处理 console.log(err.message)}
要想在前端组件初始化时就调用后端函数(比如拉取一些初始化数据),要小心pywebview还没加载好,前端还找不到后端api。可以编写下面这个钩子,用runAfterPywebview(fn)
就可以确保fn在pywebview加载后再执行:// 用于在检查pywebview加载完成后执行某个函数, timeInterval为检查间隔,timeLimit为检查超时时间export function runAfterPywebview(fn, timeInterval = 100, timeLimit = 3000) { let time2now = 0 let timer = setInterval(() => { time2now += timeInterval // 如果已经超时pywebview还没加载好,报错。 if (time2now > timeLimit) { console.log({ message: 'pywebview加载超时', showClose: true, }) clearInterval(timer) timer = null return } // 没有超时,只有当pywebview加载好才执行fn。 if (window.pywebview != undefined) { fn() clearInterval(timer) timer = null return } }, timeInterval)}
此外,还可能遇到其它问题,欢迎与笔者交流。