# app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
複製代碼
運行python app.py
,打開瀏覽器訪問http://localhost:5000/
就能夠看到頁面輸出了Hello World!
html
flask的誕生於2010年的愚人節,原本它只是做者無心間寫的一個小玩具,沒想到它卻悄悄流行起來了。漫長的8年時間,flask一直沒有發佈一個嚴肅的正式版本,可是卻不能阻擋它成了github上最受好評的Python Web框架。python
flask內核內置了兩個最重要的組件,全部其它的組件都是經過易擴展的插件系統集成進來的。這兩個內置的組件分別是werkzeug和jinja2。git
werkzeug是一個用於編寫Python WSGI程序的工具包,它的結構設計和代碼質量在開源社區廣受褒揚,其源碼被尊爲Python技術領域最值得閱讀的開源庫之一。github
# wsgi.py
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response('Hello World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)
複製代碼
運行python wsgi.py
打開瀏覽器訪問http://localhost:4000/
就能夠看到頁面輸出了Hello World!
redis
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!shell
by Alex Martelli, the author of 《Python in a Nutshell》 && 《Python Cookbook》json
jinja2是一個功能極爲強大的模板系統,它完美支持unicode中文,每一個模板都運行在安全的沙箱環境中,使用jinja2編寫的模板代碼很是優美。flask
{% extends "layout.html" %} {% block body %} <ul> {% for user in users %} <li><a href="{{ user.url }}">{{ user.username }}</a></li> {% endfor %} </ul> {% endblock %} 複製代碼
werkzeug和jinja2這兩個庫的共同特色是編寫的代碼賞心悅目,做者Armin Ronacher
選擇這兩個庫來做爲flask的基石說明做者有很是挑剔的代碼品味。那麼做者是誰呢,鐺!他是一位來自澳大利亞的帥哥!瀏覽器
好,閒話少說言歸正傳,接下來咱們開始體驗flask的神奇魅力。緩存
pip install flask
圓周率可使用正整數的平方倒數之和求得,當這個級數趨於無限時,值會愈來愈接近圓周率。
# flask_pi.py
import math
from flask import Flask, request
app = Flask(__name__)
@app.route("/pi")
def pi():
# 默認參數
n = int(request.args.get('n', '100'))
s = 0.0
for i in range(1, n):
s += 1.0/i/i
return str(math.sqrt(6*s))
if __name__ == '__main__':
app.run()
複製代碼
運行python flask_pi.py
,打開瀏覽器訪問http://localhost:5000/pi?n=1000000
,能夠看到頁面輸出3.14159169866
,這個值同圓周率已經很是接近。
注意pi()
的返回值不能是浮點數,因此必須使用str
轉換成字符串
再仔細觀察代碼,你還會注意到一個特殊的變量request
,它看起來彷佛是一個全局變量。從全局變量裏拿當前請求參數,這很是奇怪。若是在多線程環境中,該如何保證每一個線程拿到的都是當前線程正在處理的請求參數呢?因此它不能是全局變量,它是線程局部變量,線程局部變量外表上和全局變量沒有差異,可是在訪問線程局部變量時,每一個線程獲得的都是當前線程內部共享的對象。
爲了不重複計算,咱們將已經計算的pi(n)
值緩存起來,下次就能夠直接查詢。同時咱們再也不只返回一個單純的字符串,咱們返回一個json串,裏面有一個字段cached用來標識當前的結果是否從緩存中直接獲取的。
import math
import threading
from flask import Flask, request
from flask.json import jsonify
app = 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] = pi
def get(self, n):
with self.lock:
return self.pis.get(n)
cache = PiCache()
@app.route("/pi")
def pi():
n = int(request.args.get('n', '100'))
result = cache.get(n)
if result:
return jsonify({"cached": True, "result": result})
s = 0.0
for i in range(1, n):
s += 1.0/i/i
result = math.sqrt(6*s)
cache.set(n, result)
return jsonify({"cached": False, "result": result})
if __name__ == '__main__':
app.run()
複製代碼
運行python flask_pi.py
,打開瀏覽器訪問http://localhost:5000/pi?n=1000000
,能夠看到頁面輸出
{
"cached": false,
"result": 3.141591698659554
}
複製代碼
再次刷新頁面,咱們能夠觀察到cached字段變成了true,說明結果確實已經緩存了
{
"cached": true,
"result": 3.141591698659554
}
複製代碼
讀者也許會問,爲何緩存類PiCache須要使用RLock呢?這是由於考慮到多線程環境下Python的字典讀寫不是徹底線程安全的,須要使用鎖來保護一下數據結構。
上面的緩存僅僅是內存緩存,進程重啓後,緩存結果消失,下次計算又得從新開始。
if __name__ == '__main__':
app.run('127.0.0.1', 5001)
複製代碼
若是開啓第二個端口5001來提供服務,那這第二個進程也沒法享受第一個進程的內存緩存,而必須從新計算。因此這裏要引入分佈式緩存Redis來共享計算緩存,避免跨進程重複計算,避免重啓從新計算。
import math
import redis
from flask import Flask, request
from flask.json import jsonify
app = Flask(__name__)
class PiCache(object):
def __init__(self, client):
self.client = client
def 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:
return
return float(result)
client = redis.StrictRedis()
cache = PiCache(client)
@app.route("/pi")
def pi():
n = int(request.args.get('n', '100'))
result = cache.get(n)
if result:
return jsonify({"cached": True, "result": result})
s = 0.0
for i in range(1, n):
s += 1.0/i/i
result = math.sqrt(6*s)
cache.set(n, result)
return jsonify({"cached": False, "result": result})
if __name__ == '__main__':
app.run('127.0.0.1', 5000)
複製代碼
運行python flask_pi.py
,打開瀏覽器訪問http://localhost:5000/pi?n=1000000
,能夠看到頁面輸出
{
"cached": false,
"result": 3.141591698659554
}
複製代碼
再次刷新頁面,咱們能夠觀察到cached字段變成了true,說明結果確實已經緩存了
{
"cached": true,
"result": 3.141591698659554
}
複製代碼
重啓進程,再次刷新頁面,能夠看書頁面輸出的cached字段依然是true,說明緩存結果再也不由於進程重啓而丟失。
寫過Django的朋友們可能會問,Flask是否支持類形式的API編寫方式,回答是確定的。下面咱們使用Flask原生支持的MethodView來改寫一下上面的服務。
import math
import redis
from flask import Flask, request
from flask.json import jsonify
from flask.views import MethodView
app = Flask(__name__)
class PiCache(object):
def __init__(self, client):
self.client = client
def 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:
return
return float(result)
client = redis.StrictRedis()
cache = PiCache(client)
class PiAPI(MethodView):
def __init__(self, cache):
self.cache = cache
def get(self, n):
result = self.cache.get(n)
if result:
return jsonify({"cached": True, "result": result})
s = 0.0
for i in range(1, n):
s += 1.0/i/i
result = math.sqrt(6*s)
self.cache.set(n, result)
return jsonify({"cached": False, "result": result})
# as_view提供了參數能夠直接注入到MethodView的構造器中
# 咱們再也不使用request.args,而是將參數直接放進URL裏面,這就是RESTFUL風格的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)
複製代碼
咱們實現了MethodView的get方法,說明該API僅支持HTTP請求的GET方法。若是要支持POST、PUT和DELETE方法,須要用戶本身再去實現這些方法。
flask默認的MethodView挺好用,可是也不夠好用,它沒法在一個類裏提供多個不一樣URL名稱的API服務。因此接下來咱們引入flask的擴展flask-classy來解決這個問題。
使用擴展的第一步是安裝擴展pip install flask-classy
,而後咱們在同一個類裏再加一個新的API服務,計算斐波那契級數。
import math
import redis
from flask import Flask
from flask.json import jsonify
from flask_classy import FlaskView, route # 擴展
app = Flask(__name__)
# pi的cache和fib的cache要分開
class PiCache(object):
def __init__(self, client):
self.client = client
def 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:
return
return 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:
return
return float(result)
client = redis.StrictRedis()
cache = PiCache(client)
class MathAPI(FlaskView):
@route("/pi/<int:n>")
def pi(self, n):
result = cache.get_pi(n)
if result:
return jsonify({"cached": True, "result": result})
s = 0.0
for i in range(1, n):
s += 1.0/i/i
result = math.sqrt(6*s)
cache.set_pi(n, result)
return jsonify({"cached": False, "result": result})
@route("/fib/<int:n>")
def fib(self, n):
result, cached = self.get_fib(n)
return jsonify({"cached": cached, "result": result})
def get_fib(self, n): # 遞歸,n不能過大,不然會堆棧過深溢出stackoverflow
if n == 0:
return 0, True
if n == 1:
return 1, True
result = cache.get_fib(n)
if result:
return result, True
result = self.get_fib(n-1)[0] + self.get_fib(n-2)[0]
cache.set_fib(n, result)
return result, False
MathAPI.register(app, route_base='/') # 註冊到app
if __name__ == '__main__':
app.run('127.0.0.1', 5000)
複製代碼
訪問http://localhost:5000/fib/100
,咱們能夠看到頁面輸出了
{
"cached": false,
"result": 354224848179261915075
}
複製代碼
訪問http://localhost:5000/pi/10000000
,計算量比較大,因此多轉了一回,最終頁面輸出了
{
"cached": false,
"result": 3.141592558095893
}
複製代碼
閱讀更多高級文章,關注微信訂閱號「碼洞」