如何在一周内将Web应用转换为无网环境运行的GUI程序?程序员应对需求变更的实用指南
如果说有什么是程序员害怕的,那么我想可能就是——需求又变了!

不,客户在我开发基于浏览器的Web应用程序后说:该程序需要在内部(不)内部(网络)环境中运行......
这意味着Python环境无法安装!
你以为我们是程序员?我们为什么不开发一个 GUI 版本呢?对我来说并不难...
但听到规定的时间后,我就失去了冷静……
为了不影响客户评价,我们只能给1周!
构想
GUI虽然不难,但确实需要梳理好服务以及与用户的交互界面。如果没有,你就必须为GUI编写一个单独的界面,这显然是时间不够的。
不行,再想想别的办法吧……
否则,可以直接将Web应用程序打包成可执行程序,复制到机器上后即可运行。类似的框架还有很多,比如 Nodejs 中的 Electron[1] 和 Python 中的 Pywebview[2]。
只需将原来的Web程序包装起来就可以了!
神器出现
Web程序是使用Flask开发的,所以需要安装Python的Pywebview作为打包工具。
建立虚拟环境[3]或者在原来的Web项目环境中,执行:
pip install pywebview
Windows系统需要.Net 4.0以上版本
稍微测试一下你的技能:
import webview
window = webview.create_window('Hello!', 'http://http://www.justdopython.com')
webview.start()
可以看到如下效果:
测试你的技能
惊人的!
Pywebview支持三种模式,简单模式、服务器模式和线程模式。
简单模式相当于定制的流媒体浏览器。您可以通过指定地址进行浏览,如上例所示。
服务器模式相当于打包了一个Web应用程序,也就是说会启动一个本地服务器,并在定制的浏览器中进行浏览。
线程模式更高级,这意味着你需要手动维护线程状态才能实现更高级的玩法。
针对当前的需求,我们选择服务器模式,即包装本地Web应用程序。
连接到烧瓶
服务器模式将为我们提供一个HTTP Server,我们只需在其上部署Web应用程序即可。
因为它只是显示实际项目的代码,所以这里是一个简单的 Flask 应用程序:
关于Flask Web应用开发,可以参考作者之前写的Flask文章。
创建一个app.py文件:
from flask import Flask, render_template, jsonify, request
app = Flask(__name__) # 创建一个应用
@app.route('/')
def index(): # 定义根目录处理器
return render_template('index.html')
@app.route('/detail')
def detail():
return render_template('detail.html')
if __name__ == '__main__':
app.run() # 启动服务
这个应用程序非常简单,只有两个页面,通过/和/detail访问。
如果运行此代码,将启动 Flask 应用程序,通过 :5000 访问。
如何在Pywebview中安装它?
很简单:
import webview
from app import app
if __name__ == '__main__':
window = webview.create_window('Pywebview', app, height=600, width=1000)
webview.start()
这里的关键是使用 Flask 应用程序作为 url 参数。如果Webview发现传入的参数是flask应用,就会启动服务模式。
运行程序后可以看到和在浏览器中一样的效果:
连接到烧瓶
目录问题
现在你可以把这个项目打包成exe了。
首先你需要安装pyinstaller[4]
pip install pyinstaller
然后进入程序目录并执行:
pyinstall -F -w main.py
很快,程序目录下会生成一个dist文件夹,里面有一个main.exe可执行文件。这是打包后的结果。
双击运行就可以看到效果了...
等等,好像不是我想象的那样!
连接到烧瓶
这是怎么回事?
根据提示,是因为找不到页面的模板文件。
前面我们创建Flask应用程序时,使用了默认的模板路径,即app.py文件所在目录的templates目录。为什么打包后找不到?
这是因为在Windows中,可执行文件在运行时会被解压到特定目录,而我们的模板文件并没有打包到exe文件中,所以运行时找不到模板文件。
完美呈现
如何解决这个问题呢?
作为一个不使用外部数据或文件的程序,只需要对程序本身进行打包即可。然而,大多数程序需要外部数据。比如我们的Flask应用需要使用静态文件等。
那么如何将它们打包成可执行文件呢?
打包的时候多加一个参数即可:
pyinstaller main.py -F -w --add-data "./templates/*;templates"
-- add-data 参数表示添加附加数据 -- ./templates/* 表示当前目录的templates目录下的所有文件都需要添加 -- ;是分隔符,后面的模板表示解压后就是这些数据所在的目录。这个目录名必须和创建app时的template_folder参数一致——如果需要使用静态文件,需要额外添加,如--add-data "./static/*;static"
这样就可以将外部数据打包在一起。
打包完成后,双击执行,你会发现网页完美呈现。
注意:
如果使用虚拟环境,pyinstaller必须单独安装在虚拟环境中,不能安装在其他环境中。这是对程序所引用的可以链接的模块进行打包打包。
因为pyinstaller在打包时,找不到引用的模块时不会报错,但打包后的程序可能无法执行。
总结
经过一番努力,终于在客户要求的时间之前完成了工作。我很高兴。
回过头来看,得益于使用Python作为主要开发语言,没有因为Python强大的社区支持而找不到解决方案。
这次经历的另一个启示是,遇到问题时,不要着急。你可以先想想是否有更好的方法,尤其是在使用Python的时候。