99999久久久久久亚洲,欧美人与禽猛交狂配,高清日韩av在线影院,一个人在线高清免费观看,啦啦啦在线视频免费观看www

熱線電話:13121318867

登錄
首頁精彩閱讀Python小技之不用 GUI,照樣實現(xiàn)圖形界面
Python小技之不用 GUI,照樣實現(xiàn)圖形界面
2022-01-25
收藏

作者:李曉飛

來源:Python 技術(shù)

如果說程序員有什么怕的,那我想可能就是 —— 需求又變了!

這不,客戶在筆者開發(fā)完一個基于瀏覽器的 Web 應(yīng)用程序之后說:程序需要在內(nèi)(無)部(網(wǎng))環(huán)境中運行……

這就意味著無法安裝 Python 環(huán)境!

誰叫咱是程序員呢,不就開發(fā)一個 GUI 版本嗎,難不倒我……

可是聽到給的時間后,就不淡定了……

為了不影響客戶的評測,只能給出一周時間!

構(gòu)思

GUI 雖然也不難,不過需要梳理一遍服務(wù)以及與用戶的交互接口,弄不好就得為 GUI 單獨編寫接口,這點時間顯然不夠呀。

不行,就再想想辦法……

不然直接將 Web 應(yīng)用包裝成一個可執(zhí)行程序,拷貝到機器上就能運行,而且類似的框架很多,比如 Nodejs 中的 Electron[1],Python 中的 Pywebview[2]。

只要將原來的 Web 程序包裝一下就好了,那么說干就干!

神器出場

Web 程序是用 Flask 開發(fā)的,所以需要安裝 Python 的 Pywebview 作為打包工具。

建立虛擬環(huán)境[3] 或者在原來的 Web 項目環(huán)境中,執(zhí)行:

pip install pywebview

在 Windows 系統(tǒng)中,需要 .Net 4.0 以上

小試牛刀:

import webview window = webview.create_window('Hello!', 'http://http://www.justdopython.com')
webview.start()
  • 引用 webview 庫
  • 啟動一個窗口,設(shè)置標題為 Hello!,指定頁面地址
  • 啟動 webview

就能看到如下的效果:

Python小技之不用 GUI,照樣實現(xiàn)圖形界面

小試牛刀

神奇吧!

Pywebview 支持三種模式,簡單模式,服務(wù)器模式 和 線程模式。

簡單模式 就相當于一個定制流瀏覽器,指定一個地址,就可以實現(xiàn)瀏覽了,如上面的例子。

服務(wù)器模式 相當于包裝了一個 Web 應(yīng)用,就是會啟動一個本地服務(wù)器,在定制的瀏覽器中瀏覽。

線程模式 比較高級,就是需要自己手動維護線程狀態(tài),實現(xiàn)更高級的玩法。

對于現(xiàn)在的需求,我們選擇服務(wù)器模式,即包裝本地的一個 Web 應(yīng)用。

對接 Flask

服務(wù)器模式會為我們提供一個 HTTP Server,只要把 Web 應(yīng)用部署上去就好了。

因為無非展示實際項目的代碼,這里寫一個簡單的 Flask 應(yīng)用:

關(guān)于 Flask Web 應(yīng)用開發(fā),可以參考筆者之前寫的 Flask 文章

創(chuàng)建一個 app.py 文件:

from flask import Flask, render_template, jsonify, request

app = Flask(__name__) # 創(chuàng)建一個應(yīng)用 @app.route('/')  def index(): # 定義根目錄處理器 return render_template('index.html') @app.route('/detail') def detail(): return render_template('detail.html') if __name__ == '__main__':
    app.run() # 啟動服務(wù) 

這個應(yīng)用很簡單,只有兩個頁面,分別通過 / 和 /detail 來訪問。

如果運營這段代碼,就會啟動一個 Flask 應(yīng)用,通過 http://120.0.0.1:5000 來訪問。

如何套在 Pywebview 中呢?

很簡單:

import webview from app import app if __name__ == '__main__': window = webview.create_window('Pywebview', app, height=600, width=1000)
    webview.start()
  • 引入 webview
  • 引入 剛才創(chuàng)建的 app
  • 創(chuàng)建一個 webview window,并將 app 作為 url 參數(shù)傳入
  • 然后啟動 webview 就可以了

這里的關(guān)鍵是,將 Flask 應(yīng)用作為 url 參數(shù),Webview 發(fā)現(xiàn)傳入的參數(shù)是 flask 應(yīng)用,就會啟動服務(wù)模式。

運行程序后,可以看到和在瀏覽器中的效果一樣的:

Python小技之不用 GUI,照樣實現(xiàn)圖形界面

對接 Flask

目錄問題

現(xiàn)在就可以將這個項目打包成 exe 了。

首先需要安裝 pyinstaller[4]

pip install pyinstaller

然后進入程序目錄執(zhí)行:

pyinstall -F -w main.py 
  • F 參數(shù)表示將程序打包成一個可執(zhí)行文件,不加這個參數(shù)就會打包成一個文件夾夾
  • w 參數(shù)表示執(zhí)行打包好的可執(zhí)行程序時,不顯示命令行窗口,這個特性只有在 Windows 系統(tǒng)中有

很快在程序目錄下,就會生成一個 dist 文件夾,其中就會有個 main.exe 可執(zhí)行文件,這就是打包好的結(jié)果。

雙擊運行,可以看到效果……

等等,好像并不是想象中的那樣!

Python小技之不用 GUI,照樣實現(xiàn)圖形界面

對接 Flask

這是怎么回事呢?

根據(jù)提示來看,是因為找不到頁面的模板文件。

我們在前面創(chuàng)建 Flask app 時,使用的是默認的模板路徑,即 app.py 文件所在目錄的 templates 目錄,為啥打包之后就找不見了呢?

這是因為在 windows 中,可執(zhí)行文件的運行時,會被解壓到一個特定的目錄下,而我們的模板文件并沒有被打包進入 exe 文件中,所以導(dǎo)致運行時找不見模板文件。

完美呈現(xiàn)

如何解決這個問題呢?

作為不使用外部數(shù)據(jù)或文件的程序,只需要將程序本身打包就可以了,但大部分程序都需要外部數(shù)據(jù),比如我們的 Flask 應(yīng)用,就需要用到靜態(tài)文件等。

那么如何將它們打包進可執(zhí)行文件呢?

只需要在打包時多加一個參數(shù)就可以了:

pyinstaller main.py -F -w --add-data "./templates/*;templates" 

-- add-data 參數(shù)表示添加額外的數(shù)據(jù) -- ./templates/* 表示需要添加當前目錄的 templates 目錄中的所有文件 -- ;為分隔符,其后的 templates 表示解壓是這些數(shù)據(jù)所在的目錄,這個目錄名必須和 創(chuàng)建 app 時 template_folder 參數(shù)一致 -- 如果需要用到靜態(tài)文件,需要額外添加,比如 --add-data "./static/*;static"

這樣就能將外部數(shù)據(jù)一起打包進來了。

打包好后,雙擊執(zhí)行,就會發(fā)現(xiàn)網(wǎng)頁得以完美呈現(xiàn)了。

注意:

如果使用了虛擬環(huán)境,必須在虛擬環(huán)境中單獨安裝 pyinstaller,而不能用其他環(huán)境中已經(jīng)安裝好的,這是為了包裝打包是可以鏈接所以程序引用的模塊

因為 pyinstaller 打包時,找不到被引用的模塊時并不報錯,而打包好的程序可能會無法執(zhí)行。

總結(jié)

經(jīng)過一番折騰,終于在客戶要求的時間之前將工作完成了,特別高興。

回頭一想,多虧用了 Python 作為主要的開發(fā)語言,因為 Python 強悍的社區(qū)支持沒有找不到的解決方法。

這次經(jīng)歷的另一個啟示就是,遇到問題,不要著急就做,可以先想一想,是否有更好的方法,特別在使用 Python 的時候。

Python小技之不用 GUI,照樣實現(xiàn)圖形界面

數(shù)據(jù)分析咨詢請掃描二維碼

若不方便掃碼,搜微信號:CDAshujufenxi

數(shù)據(jù)分析師資訊
更多

OK
客服在線
立即咨詢
客服在線
立即咨詢
') } function initGt() { var handler = function (captchaObj) { captchaObj.appendTo('#captcha'); captchaObj.onReady(function () { $("#wait").hide(); }).onSuccess(function(){ $('.getcheckcode').removeClass('dis'); $('.getcheckcode').trigger('click'); }); window.captchaObj = captchaObj; }; $('#captcha').show(); $.ajax({ url: "/login/gtstart?t=" + (new Date()).getTime(), // 加隨機數(shù)防止緩存 type: "get", dataType: "json", success: function (data) { $('#text').hide(); $('#wait').show(); // 調(diào)用 initGeetest 進行初始化 // 參數(shù)1:配置參數(shù) // 參數(shù)2:回調(diào),回調(diào)的第一個參數(shù)驗證碼對象,之后可以使用它調(diào)用相應(yīng)的接口 initGeetest({ // 以下 4 個配置參數(shù)為必須,不能缺少 gt: data.gt, challenge: data.challenge, offline: !data.success, // 表示用戶后臺檢測極驗服務(wù)器是否宕機 new_captcha: data.new_captcha, // 用于宕機時表示是新驗證碼的宕機 product: "float", // 產(chǎn)品形式,包括:float,popup width: "280px", https: true // 更多配置參數(shù)說明請參見:http://docs.geetest.com/install/client/web-front/ }, handler); } }); } function codeCutdown() { if(_wait == 0){ //倒計時完成 $(".getcheckcode").removeClass('dis').html("重新獲取"); }else{ $(".getcheckcode").addClass('dis').html("重新獲取("+_wait+"s)"); _wait--; setTimeout(function () { codeCutdown(); },1000); } } function inputValidate(ele,telInput) { var oInput = ele; var inputVal = oInput.val(); var oType = ele.attr('data-type'); var oEtag = $('#etag').val(); var oErr = oInput.closest('.form_box').next('.err_txt'); var empTxt = '請輸入'+oInput.attr('placeholder')+'!'; var errTxt = '請輸入正確的'+oInput.attr('placeholder')+'!'; var pattern; if(inputVal==""){ if(!telInput){ errFun(oErr,empTxt); } return false; }else { switch (oType){ case 'login_mobile': pattern = /^1[3456789]\d{9}$/; if(inputVal.length==11) { $.ajax({ url: '/login/checkmobile', type: "post", dataType: "json", data: { mobile: inputVal, etag: oEtag, page_ur: window.location.href, page_referer: document.referrer }, success: function (data) { } }); } break; case 'login_yzm': pattern = /^\d{6}$/; break; } if(oType=='login_mobile'){ } if(!!validateFun(pattern,inputVal)){ errFun(oErr,'') if(telInput){ $('.getcheckcode').removeClass('dis'); } }else { if(!telInput) { errFun(oErr, errTxt); }else { $('.getcheckcode').addClass('dis'); } return false; } } return true; } function errFun(obj,msg) { obj.html(msg); if(msg==''){ $('.login_submit').removeClass('dis'); }else { $('.login_submit').addClass('dis'); } } function validateFun(pat,val) { return pat.test(val); }