視圖裝飾器

Python 有一個非常有趣的功能叫做函數裝飾器。這為 Web 應用程式帶來了一些非常棒的功能。因為 Flask 中的每個視圖都是一個函數,所以裝飾器可以用來將額外的功能注入到一個或多個函數中。您可能已經使用過 route() 裝飾器。但是,在某些情況下,您需要實作自己的裝飾器。例如,假設您有一個視圖應該只供已登入的使用者使用。如果使用者訪問該網站但未登入,則應將其重新導向到登入頁面。這是一個很好的用例範例,裝飾器是一個絕佳的解決方案。

需要登入裝飾器

那麼,讓我們實作這樣一個裝飾器。裝飾器是一個包裝和取代另一個函數的函數。由於原始函數被取代了,您需要記住將原始函數的資訊複製到新函數中。使用 functools.wraps() 來為您處理這個問題。

此範例假設登入頁面稱為 'login',並且目前使用者儲存在 g.user 中,如果沒有人登入,則為 None

from functools import wraps
from flask import g, request, redirect, url_for

def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if g.user is None:
            return redirect(url_for('login', next=request.url))
        return f(*args, **kwargs)
    return decorated_function

要使用裝飾器,請將其作為最內層的裝飾器應用於視圖函數。當應用更多裝飾器時,請始終記住 route() 裝飾器是最外層的。

@app.route('/secret_page')
@login_required
def secret_page():
    pass

注意

next 值將在登入頁面的 GET 請求之後存在於 request.args 中。您必須在從登入表單發送 POST 請求時將其傳遞下去。您可以使用隱藏的輸入標籤來執行此操作,然後在使用者登入時從 request.form 中檢索它。

<input type="hidden" value="{{ request.args.get('next', '') }}"/>

快取裝飾器

假設您有一個視圖函數執行了昂貴的計算,因此您想快取產生的結果一段時間。裝飾器對於此目的會很好用。我們假設您已設定了如 快取 中所述的快取。

這是一個快取函數的範例。它從特定的前綴(實際上是一個格式字串)和請求的當前路徑生成快取鍵。請注意,我們正在使用一個首先建立裝飾器,然後裝飾函數的函數。聽起來很糟糕嗎?不幸的是,它有點複雜,但是程式碼仍然應該很容易閱讀。

裝飾後的函數將按如下方式工作

  1. 根據當前路徑取得當前請求的唯一快取鍵。

  2. 從快取中取得該鍵的值。如果快取返回了某些內容,我們將返回該值。

  3. 否則,將呼叫原始函數,並且傳回值將儲存在快取中,超時時間為提供的時間(預設為 5 分鐘)。

這是程式碼

from functools import wraps
from flask import request

def cached(timeout=5 * 60, key='view/{}'):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            cache_key = key.format(request.path)
            rv = cache.get(cache_key)
            if rv is not None:
                return rv
            rv = f(*args, **kwargs)
            cache.set(cache_key, rv, timeout=timeout)
            return rv
        return decorated_function
    return decorator

請注意,這假設已實例化 cache 物件可用,請參閱 快取

範本裝飾器

TurboGears 的人員在不久前發明了一種常見模式,即範本裝飾器。該裝飾器的想法是,您從視圖函數返回一個包含傳遞給範本的值的字典,並且範本會自動呈現。這樣,以下三個範例的功能完全相同

@app.route('/')
def index():
    return render_template('index.html', value=42)

@app.route('/')
@templated('index.html')
def index():
    return dict(value=42)

@app.route('/')
@templated()
def index():
    return dict(value=42)

如您所見,如果未提供範本名稱,它將使用 URL 對應的端點,並將點轉換為斜線 + '.html'。否則,將使用提供的範本名稱。當裝飾後的函數返回時,傳回的字典將傳遞給範本呈現函數。如果返回 None,則假定為空字典;如果返回字典以外的其他內容,我們將從函數中傳回未更改的內容。這樣,您仍然可以使用重新導向函數或傳回簡單的字串。

這是該裝飾器的程式碼

from functools import wraps
from flask import request, render_template

def templated(template=None):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            template_name = template
            if template_name is None:
                template_name = f"{request.endpoint.replace('.', '/')}.html"
            ctx = f(*args, **kwargs)
            if ctx is None:
                ctx = {}
            elif not isinstance(ctx, dict):
                return ctx
            return render_template(template_name, **ctx)
        return decorated_function
    return decorator

端點裝飾器

當您想要使用 werkzeug 路由系統以獲得更大的靈活性時,您需要將 Rule 中定義的端點對應到視圖函數。這可以使用此裝飾器來實現。例如

from flask import Flask
from werkzeug.routing import Rule

app = Flask(__name__)
app.url_map.add(Rule('/', endpoint='index'))

@app.endpoint('index')
def my_index():
    return "Hello world"