Github上最受欢迎的Python轻量级框架Flask入门
運(yùn)行python app.py,打開瀏覽器訪問(wèn)http://localhost:5000/就可以看到頁(yè)面輸出了Hello World!
flask的誕生于2010年的愚人節(jié),本來(lái)它只是作者無(wú)意間寫的一個(gè)小玩具,沒(méi)想到它卻悄悄流行起來(lái)了。漫長(zhǎng)的8年時(shí)間,flask一直沒(méi)有發(fā)布一個(gè)嚴(yán)肅的正式版本,但是卻不能阻擋它成了github上最受好評(píng)的Python Web框架。
flask內(nèi)核內(nèi)置了兩個(gè)最重要的組件,所有其它的組件都是通過(guò)易擴(kuò)展的插件系統(tǒng)集成進(jìn)來(lái)的。這兩個(gè)內(nèi)置的組件分別是werkzeug和jinja2。
werkzeug是一個(gè)用于編寫Python WSGI程序的工具包,它的結(jié)構(gòu)設(shè)計(jì)和代碼質(zhì)量在開源社區(qū)廣受褒揚(yáng),其源碼被尊為Python技術(shù)領(lǐng)域最值得閱讀的開源庫(kù)之一。
# wsgi.py from werkzeug.wrappers import Request, Response def application(request):return Response('Hello World!')if __name__ == '__main__':from werkzeug.serving import run_simplerun_simple('localhost', 4000, application) 復(fù)制代碼運(yùn)行python wsgi.py打開瀏覽器訪問(wèn)http://localhost:4000/就可以看到頁(yè)面輸出了Hello World!
Have you looked at werkzeug.routing? It's hard to find anything that's simpler, more self-contained, or purer-WSGI than Werkzeug, in general — I'm quite a fan of it!
by Alex Martelli, the author of 《Python in a Nutshell》 && 《Python Cookbook》
jinja2是一個(gè)功能極為強(qiáng)大的模板系統(tǒng),它完美支持unicode中文,每個(gè)模板都運(yùn)行在安全的沙箱環(huán)境中,使用jinja2編寫的模板代碼非常優(yōu)美。
{% extends "layout.html" %} {% block body %}<ul>{% for user in users %}<li><a href="{{ user.url }}">{{ user.username }}</a></li>{% endfor %}</ul> {% endblock %} 復(fù)制代碼werkzeug和jinja2這兩個(gè)庫(kù)的共同特點(diǎn)是編寫的代碼賞心悅目,作者Armin Ronacher選擇這兩個(gè)庫(kù)來(lái)作為flask的基石說(shuō)明作者有非常挑剔的代碼品味。那么作者是誰(shuí)呢,鐺!他是一位來(lái)自澳大利亞的帥哥!
好,閑話少說(shuō)言歸正傳,接下來(lái)我們開始體驗(yàn)flask的神奇魅力。
安裝flask
pip install flask
圓周率計(jì)算API
圓周率可以使用正整數(shù)的平方倒數(shù)之和求得,當(dāng)這個(gè)級(jí)數(shù)趨于無(wú)限時(shí),值會(huì)越來(lái)越接近圓周率。
# flask_pi.py import mathfrom flask import Flask, requestapp = Flask(__name__)@app.route("/pi") def pi():# 默認(rèn)參數(shù)n = int(request.args.get('n', '100'))s = 0.0for i in range(1, n):s += 1.0/i/ireturn str(math.sqrt(6*s))if __name__ == '__main__':app.run() 復(fù)制代碼運(yùn)行python flask_pi.py,打開瀏覽器訪問(wèn)http://localhost:5000/pi?n=1000000,可以看到頁(yè)面輸出3.14159169866,這個(gè)值同圓周率已經(jīng)非常接近。
注意pi()的返回值不能是浮點(diǎn)數(shù),所以必須使用str轉(zhuǎn)換成字符串
再仔細(xì)觀察代碼,你還會(huì)注意到一個(gè)特殊的變量request,它看起來(lái)似乎是一個(gè)全局變量。從全局變量里拿當(dāng)前請(qǐng)求參數(shù),這非常奇怪。如果在多線程環(huán)境中,該如何保證每個(gè)線程拿到的都是當(dāng)前線程正在處理的請(qǐng)求參數(shù)呢?所以它不能是全局變量,它是線程局部變量,線程局部變量外表上和全局變量沒(méi)有差別,但是在訪問(wèn)線程局部變量時(shí),每個(gè)線程得到的都是當(dāng)前線程內(nèi)部共享的對(duì)象。
緩存計(jì)算結(jié)果
為了避免重復(fù)計(jì)算,我們將已經(jīng)計(jì)算的pi(n)值緩存起來(lái),下次就可以直接查詢。同時(shí)我們不再只返回一個(gè)單純的字符串,我們返回一個(gè)json串,里面有一個(gè)字段cached用來(lái)標(biāo)識(shí)當(dāng)前的結(jié)果是否從緩存中直接獲取的。
import math import threadingfrom flask import Flask, request from flask.json import jsonifyapp = Flask(__name__)class PiCache(object):def __init__(self):self.pis = {}self.lock = threading.RLock()def set(self, n, pi):with self.lock:self.pis[n] = pidef get(self, n):with self.lock:return self.pis.get(n)cache = PiCache() def pi():n = int(request.args.get('n', '100'))result = cache.get(n)if result:return jsonify({"cached": True, "result": result})s = 0.0for i in range(1, n):s += 1.0/i/iresult = math.sqrt(6*s)cache.set(n, result)return jsonify({"cached": False, "result": result})if __name__ == '__main__':app.run() 復(fù)制代碼運(yùn)行python flask_pi.py,打開瀏覽器訪問(wèn)http://localhost:5000/pi?n=1000000,可以看到頁(yè)面輸出
{"cached": false,"result": 3.141591698659554 } 復(fù)制代碼再次刷新頁(yè)面,我們可以觀察到cached字段變成了true,說(shuō)明結(jié)果確實(shí)已經(jīng)緩存了
{"cached": true,"result": 3.141591698659554 } 復(fù)制代碼讀者也許會(huì)問(wèn),為什么緩存類PiCache需要使用RLock呢?這是因?yàn)榭紤]到多線程環(huán)境下Python的字典讀寫不是完全線程安全的,需要使用鎖來(lái)保護(hù)一下數(shù)據(jù)結(jié)構(gòu)。
分布式緩存
上面的緩存僅僅是內(nèi)存緩存,進(jìn)程重啟后,緩存結(jié)果消失,下次計(jì)算又得重新開始。
if __name__ == '__main__':app.run('127.0.0.1', 5001) 復(fù)制代碼如果開啟第二個(gè)端口5001來(lái)提供服務(wù),那這第二個(gè)進(jìn)程也無(wú)法享受第一個(gè)進(jìn)程的內(nèi)存緩存,而必須重新計(jì)算。所以這里要引入分布式緩存Redis來(lái)共享計(jì)算緩存,避免跨進(jìn)程重復(fù)計(jì)算,避免重啟重新計(jì)算。
import math import redisfrom flask import Flask, request from flask.json import jsonifyapp = Flask(__name__)class PiCache(object):def __init__(self, client):self.client = clientdef set(self, n, result):self.client.hset("pis", str(n), str(result))def get(self, n):result = self.client.hget("pis", str(n))if not result:returnreturn float(result)client = redis.StrictRedis() cache = PiCache(client) def pi():n = int(request.args.get('n', '100'))result = cache.get(n)if result:return jsonify({"cached": True, "result": result})s = 0.0for i in range(1, n):s += 1.0/i/iresult = math.sqrt(6*s)cache.set(n, result)return jsonify({"cached": False, "result": result})if __name__ == '__main__':app.run('127.0.0.1', 5000) 復(fù)制代碼運(yùn)行python flask_pi.py,打開瀏覽器訪問(wèn)http://localhost:5000/pi?n=1000000,可以看到頁(yè)面輸出
{"cached": false,"result": 3.141591698659554 } 復(fù)制代碼再次刷新頁(yè)面,我們可以觀察到cached字段變成了true,說(shuō)明結(jié)果確實(shí)已經(jīng)緩存了
{"cached": true,"result": 3.141591698659554 } 復(fù)制代碼重啟進(jìn)程,再次刷新頁(yè)面,可以看書頁(yè)面輸出的cached字段依然是true,說(shuō)明緩存結(jié)果不再因?yàn)檫M(jìn)程重啟而丟失。
MethodView
寫過(guò)Django的朋友們可能會(huì)問(wèn),Flask是否支持類形式的API編寫方式,回答是肯定的。下面我們使用Flask原生支持的MethodView來(lái)改寫一下上面的服務(wù)。
import math import redisfrom flask import Flask, request from flask.json import jsonify from flask.views import MethodViewapp = Flask(__name__)class PiCache(object):def __init__(self, client):self.client = clientdef set(self, n, result):self.client.hset("pis", str(n), str(result))def get(self, n):result = self.client.hget("pis", str(n))if not result:returnreturn float(result)client = redis.StrictRedis() cache = PiCache(client)class PiAPI(MethodView):def __init__(self, cache):self.cache = cachedef get(self, n):result = self.cache.get(n)if result:return jsonify({"cached": True, "result": result})s = 0.0for i in range(1, n):s += 1.0/i/iresult = math.sqrt(6*s)self.cache.set(n, result)return jsonify({"cached": False, "result": result})# as_view提供了參數(shù)可以直接注入到MethodView的構(gòu)造器中 # 我們不再使用request.args,而是將參數(shù)直接放進(jìn)URL里面,這就是RESTFUL風(fēng)格的URL app.add_url_rule('/pi/<int:n>', view_func=PiAPI.as_view('pi', cache))if __name__ == '__main__':app.run('127.0.0.1', 5000) 復(fù)制代碼我們實(shí)現(xiàn)了MethodView的get方法,說(shuō)明該API僅支持HTTP請(qǐng)求的GET方法。如果要支持POST、PUT和DELETE方法,需要用戶自己再去實(shí)現(xiàn)這些方法。
flask默認(rèn)的MethodView挺好用,但是也不夠好用,它無(wú)法在一個(gè)類里提供多個(gè)不同URL名稱的API服務(wù)。所以接下來(lái)我們引入flask的擴(kuò)展flask-classy來(lái)解決這個(gè)問(wèn)題。
小試flask擴(kuò)展flask-classy
使用擴(kuò)展的第一步是安裝擴(kuò)展pip install flask-classy,然后我們?cè)谕粋€(gè)類里再加一個(gè)新的API服務(wù),計(jì)算斐波那契級(jí)數(shù)。
import math import redisfrom flask import Flask from flask.json import jsonify from flask_classy import FlaskView, route # 擴(kuò)展app = Flask(__name__)# pi的cache和fib的cache要分開 class PiCache(object):def __init__(self, client):self.client = clientdef set_fib(self, n, result):self.client.hset("fibs", str(n), str(result))def get_fib(self, n):result = self.client.hget("fibs", str(n))if not result:returnreturn int(result)def set_pi(self, n, result):self.client.hset("pis", str(n), str(result))def get_pi(self, n):result = self.client.hget("pis", str(n))if not result:returnreturn float(result)client = redis.StrictRedis() cache = PiCache(client)class MathAPI(FlaskView):def pi(self, n):result = cache.get_pi(n)if result:return jsonify({"cached": True, "result": result})s = 0.0for i in range(1, n):s += 1.0/i/iresult = math.sqrt(6*s)cache.set_pi(n, result)return jsonify({"cached": False, "result": result})def fib(self, n):result, cached = self.get_fib(n)return jsonify({"cached": cached, "result": result})def get_fib(self, n): # 遞歸,n不能過(guò)大,否則會(huì)堆棧過(guò)深溢出stackoverflowif n == 0:return 0, Trueif n == 1:return 1, Trueresult = cache.get_fib(n)if result:return result, Trueresult = self.get_fib(n-1)[0] + self.get_fib(n-2)[0]cache.set_fib(n, result)return result, FalseMathAPI.register(app, route_base='/') # 注冊(cè)到appif __name__ == '__main__':app.run('127.0.0.1', 5000) 復(fù)制代碼訪問(wèn)http://localhost:5000/fib/100,我們可以看到頁(yè)面輸出了
{"cached": false,"result": 354224848179261915075 } 復(fù)制代碼訪問(wèn)http://localhost:5000/pi/10000000,計(jì)算量比較大,所以多轉(zhuǎn)了一回,最終頁(yè)面輸出了
{"cached": false,"result": 3.141592558095893 } 復(fù)制代碼高級(jí)文章,關(guān)注微信訂閱號(hào)「碼洞」
擴(kuò)展閱讀
廖雪峰教你ThreadLocal的正確用法
Python字典是否是線程安全的
Hello Flask知乎專欄
總結(jié)
以上是生活随笔為你收集整理的Github上最受欢迎的Python轻量级框架Flask入门的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: KubeSphere 3.3.0 离线安
- 下一篇: 统计学习方法+Python机器学习实践指