Github上最受歡迎的Python輕量級框架Flask入門

flask最近終於發佈了它的1.0版本更新,從項目開源到最近的1.0版本flask已經走過了8個年頭。

# 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的神奇魅力。緩存

安裝flask

pip install flask

圓周率計算API

圓周率可使用正整數的平方倒數之和求得,當這個級數趨於無限時,值會愈來愈接近圓周率。

# 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,說明緩存結果再也不由於進程重啓而丟失。

MethodView

寫過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來解決這個問題。

小試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
}
複製代碼

閱讀更多高級文章,關注微信訂閱號「碼洞

擴展閱讀

廖雪峯教你ThreadLocal的正確用法

Python字典是不是線程安全的

Hello Flask知乎專欄

相關文章
相關標籤/搜索