視圖裝飾器
Python 裝飾器是用于轉(zhuǎn)換其它函數(shù)的函數(shù)已艰。當(dāng)一個裝飾的函數(shù)被調(diào)用的時候,裝飾器也會被調(diào)用檀蹋。接著裝飾器就會采取行動,修改參數(shù)云芦,停止執(zhí)行或者調(diào)用原始函數(shù)俯逾。我們可以使用裝飾器來包裝視圖,讓它們在執(zhí)行之前運(yùn)行我們希望的代碼焕数。
@decorator_function
def decorated():
pass
如果你已經(jīng)瀏覽了 Flask 教程,在這個代碼塊的語法看起來很熟悉刨啸。@app.route 是用于為 Flask 應(yīng)用程序的視圖函數(shù)匹配 URLs 的裝飾器堡赔。
讓我們看看其它的裝飾器,你可能會在你的 Flask 應(yīng)用中使用到它們设联。
認(rèn)證
Flask-Login 擴(kuò)展可以很容易地實(shí)現(xiàn)登錄系統(tǒng)善已。除了處理用戶認(rèn)證的細(xì)節(jié),F(xiàn)lask-Login 提供給我們一個裝飾器离例,它用來限制只允許登錄的用戶訪問某些視圖: @login_required
换团。
# app.py
from flask import render_template
from flask.ext.login import login_required, current_user
@app.route('/')
def index():
return render_template("index.html")
@app.route('/dashboard')
@login_required
def account():
return render_template("account.html")
<strong>
@app.route
應(yīng)該是外層的視圖裝飾器(換句話說, @app.route
應(yīng)該在所有裝飾器的最前面)宫蛆。
</strong>
一個登錄過的用戶才能夠訪問 /dashboard 路由艘包。我們可以配置 Flask-Login的猛,讓未登錄的用戶重定向到一個登錄頁,返回一個 HTTP 401 狀態(tài)碼或者我們想要它做的任何東西想虎。
緩存
想象下在 CNN 以及其它一些新聞網(wǎng)站中提到我們的應(yīng)用程序卦尊,我們可能會在不久之后接收每秒數(shù)千次的請求。我們的主頁針對每一次請求都要多次訪問數(shù)據(jù)庫舌厨,因此所有這些因素都會導(dǎo)致系統(tǒng)越來越慢岂却,用戶訪問等待的時間越來越長。如何才能加快訪問速度裙椭,讓所有的訪客都不會錯過我們的網(wǎng)站躏哩?
有很多好的答案,但是這一章是關(guān)于緩存揉燃,因此我們就來討論它扫尺。確切地來說,我們將要使用 Flask-Cache 擴(kuò)展你雌。這個擴(kuò)展提供我們一個裝飾器器联,我們可以在我們的首頁視圖上使用這個裝飾器用來在一段時間內(nèi)緩存響應(yīng)。
Flask-Cache 可以被配置成與一堆不同的緩存后端一起工作婿崭。一個流行的選擇是 Redis拨拓,Redis 很容易設(shè)置和使用。假設(shè) Flask-Cache 已經(jīng)配置好氓栈,這個代碼塊顯示我們的緩存裝飾器視圖是什么樣子的渣磷。
# app.py
from flask.ext.cache import Cache
from flask import Flask
app = Flask()
# We'd normally include configuration settings in this call
cache = Cache(app)
@app.route('/')
@cache.cached(timeout=60)
def index():
[...] # Make a few database calls to get the information we need
return render_template(
'index.html',
latest_posts=latest_posts,
recent_users=recent_users,
recent_photos=recent_photos
)
現(xiàn)在函數(shù)將每 60 秒會執(zhí)行一次,因?yàn)?60 秒后緩存就過期授瘦。響應(yīng)將會保存在我們的緩存中醋界,在緩存沒有過期之前,所有針對首頁的請求都會直接從緩存中讀取提完。
Flask-Cache 也為我們提供了 memoize 函數(shù) — 或者緩存一個函數(shù)調(diào)用某些參數(shù)的結(jié)果形纺。你甚至可以緩存計算開銷很高的 Jinja2 模板片段。
自定義裝飾器
對于這篇文章徒欣,先讓我們想象下我們有一個應(yīng)用程序逐样,該應(yīng)用程序每個月都會向用戶收費(fèi)。如果用戶的賬號已經(jīng)過期打肝,我們將會重定向到收費(fèi)頁面并且讓用戶升級脂新。
# myapp/util.py
from functools import wraps
from datetime import datetime
from flask import flash, redirect, url_for
from flask.ext.login import current_user
def check_expired(func):
@wraps(func)
def decorated_function(*args, **kwargs):
if datetime.utcnow() > current_user.account_expires:
flash("Your account has expired. Update your billing info.")
return redirect(url_for('account_billing'))
return func(*args, **kwargs)
return decorated_function
- 當(dāng)一個函數(shù)使用
@check_expired
裝飾,check_expired()
被調(diào)用并且被裝飾的 函數(shù)被作為參數(shù)進(jìn)行傳遞粗梭。 -
@wraps
是一個裝飾器争便,它做了一些工作使得decorated_function()
看起來像func()
。這使得函數(shù)的行為多了幾分自然断医。 -
decorated_function
將會獲取所有我們傳遞給原始視圖函數(shù)func()
的args
和kwargs
滞乙。我們在這里檢查用戶的賬號是否過期奏纪。如果已經(jīng)過期的話, 我們將會閃現(xiàn)一條消息并且重定向到一個收費(fèi)頁面酷宵。 - 現(xiàn)在我們已經(jīng)做了我們想要做的事情亥贸,我們使用它原始的參數(shù)運(yùn)行被裝飾的視圖函數(shù)
func()
。
當(dāng)我們疊加裝飾器的時候浇垦,最上層的裝飾器會首先運(yùn)行炕置,接著調(diào)用下一行的下一個函數(shù):要么是視圖函數(shù),要么就是裝飾器男韧。裝飾器的語法只是 Python 提供的一個語法糖朴摊。
# This code:
@foo
@bar
def one():
pass
r1 = one()
# is the same as this code:
def two():
pass
two = foo(bar(two))
r2 = two()
r1 == r2 # True
此代碼塊展示了一個使用我們自定義的裝飾器和來自 Flask-Login 擴(kuò)展的 @login_required 裝飾器的示例。我們可以通過疊加使用多個裝飾器此虑。
# myapp/views.py
from flask import render_template
from flask.ext.login import login_required
from . import app
from .util import check_expired
@app.route('/use_app')
@login_required
@check_expired
def use_app():
"""Use our amazing app."""
# [...]
return render_template('use_app.html')
@app.route('/account/billing')
@login_required
def account_billing():
"""Update your billing info."""
# [...]
return render_template('account/billing.html')
現(xiàn)在當(dāng)一個用戶試圖訪問 /use_app甚纲,check_expired() 將會確保在運(yùn)行視圖函數(shù)之前用戶的賬號沒有過期。
URL 轉(zhuǎn)換器(converters)
內(nèi)置轉(zhuǎn)換器(converters)
當(dāng)你在 Flask 中定義路由的時候朦前,你可以指定路由的一部分介杆,它們將會轉(zhuǎn)換成 Python 變量并且傳遞到視圖函數(shù)。
@app.route('/user/<username>')
def profile(username):
pass
在 URL 中的 <username> 將會作為 username 參數(shù)傳入到視圖韭寸。你也可以指定一個轉(zhuǎn)換器春哨,用來在變量傳入視圖之前對其進(jìn)行過濾篩選。
@app.route('/user/id/<int:user_id>')
def profile(user_id):
pass
在這個代碼塊中恩伺,URL:http://myapp.com/user/id/Q29kZUxlc3NvbiEh 將會返回一個 404 狀態(tài)碼 – 未找到赴背。這是因?yàn)?URL 中的 user_id 要求的是一個整數(shù)但實(shí)際上是一個字符串。
我們也可以有第二個視圖用來處理 user_id 為字符串晶渠,/user/id/Q29kZUxlc3NvbiEh/ 可以調(diào)用該視圖而 /user/id/124 可以調(diào)用第一個視圖凰荚。
下面描述了 Flask 內(nèi)置的 URL 轉(zhuǎn)換器。
- string: 不帶斜杠(默認(rèn)值)的任何文本褒脯。
- int: 整數(shù)便瑟。
- float: 像 int,但是只允許浮點(diǎn)值番川。
- path:像字符串到涂,但是包含斜杠。
自定義轉(zhuǎn)換器(converters)
我們也能準(zhǔn)備自定義轉(zhuǎn)換器來滿足自己的需求爽彤。在 Reddit 上 — 一個受歡迎的鏈接共享網(wǎng)站 — 用戶創(chuàng)建和主持的以主題為基礎(chǔ)的討論和鏈接共享的社區(qū)养盗。例如缚陷,/r/python 和 /r/flask 就是分別用 URL:redit.com/r/python 和 reddit.com/r/flask 來表示适篙。Reddit 一個有意思的功能就是你可以查看多個 subreddits 的文章,通過在 URL 中使用加號(+)來連接每一個 subreddits 的名稱箫爷,例如嚷节,reddit.com/r/python+flask聂儒。
我們可以在我們自己的 Flask 應(yīng)用程序中使用一個自定義的轉(zhuǎn)換器來實(shí)現(xiàn)這個功能。我們將接受通過加號(+)分離的任意數(shù)量的元素硫痰,轉(zhuǎn)換它們成一個列表(這里實(shí)現(xiàn)了一個叫做 ListConverter 的類)并且把列表元素傳給視圖函數(shù)衩婚。
# myapp/util.py
from werkzeug.routing import BaseConverter
class ListConverter(BaseConverter):
def to_python(self, value):
return value.split('+')
def to_url(self, values):
return '+'.join(BaseConverter.to_url(value)
for value in values)
我們需要定義兩個方法:to_python()
和 to_url()
。正如名稱暗示的一樣效斑,to_python()
是用于轉(zhuǎn)換 URL 中的路徑成為一個 Python 對象非春,該對象將會傳遞給視圖;to_url()
是被 url_for()
用來把參數(shù)轉(zhuǎn)換為合適的形式的 URL缓屠。
為了使用我們的 ListConverter奇昙,我們首先必須告訴 Flask 它的存在。
# /myapp/__init__.py
from flask import Flask
app = Flask(__name__)
from .util import ListConverter
app.url_map.converters['list'] = ListConverter
這里可能有機(jī)會碰到循環(huán)導(dǎo)入的問題如果你的 util 模塊有 from . import app 這一行敌完。這是我為什么要等到 app 已經(jīng)初始化后才導(dǎo)入 ListConverter储耐。
現(xiàn)在我們就可以像使用內(nèi)置的轉(zhuǎn)換器一樣使用自己的轉(zhuǎn)換器。我們可以在 @app.route() 中使用 “l(fā)ist”滨溉,就像使用內(nèi)置的 int什湘,float,string晦攒,path 一樣闽撤。
# myapp/views.py
from . import app
@app.route('/r/\\<list:subreddits\\>')
def subreddit_home(subreddits):
"""Show all of the posts for the given subreddits."""
posts = []
for subreddit in subreddits:
posts.extend(subreddit.posts)
return render_template('/r/index.html', posts=posts)
這應(yīng)該會像 Reddit 的多 reddit 系統(tǒng)一樣工作。同樣的方法可以被使用來做我們想要的任何 URL 轉(zhuǎn)換勤家。
摘要
- Flask-Login 中的 @login_required 裝飾器幫助你限制只允許登錄的用戶訪問視圖腹尖。
- Flask-Cache 擴(kuò)展為你提供了大量的裝飾器用來實(shí)現(xiàn)各種的緩存方法。
- 我們能夠開發(fā)自定義視圖裝飾器用來幫助我們組織代碼并且堅持 DRY(不要重復(fù)你自己)的編碼原則伐脖。
- 自定義的 URL 轉(zhuǎn)換器是實(shí)現(xiàn)涉及到 URL 的創(chuàng)新功能的一個很好的方式热幔。