天天看點

BUG 小記:Flask使用自定義裝飾器問題描述問題思考解決方案參考文檔

問題描述

Flask Web 項目,遇到一個需求:

部分視圖,隻有已經登入的使用者才能通路,如果使用者通路時沒有登入,則會重定向到登入頁面。

手動為每個視圖函數添加判讀語句太過繁瑣,遂決定使用裝飾器實作這個功能,代碼大概如下所示:

from flask import Flask,request,redirect

app = Flask(__name__)

def check_login_status(func):
    def wrapper(*args, **kwargs):
        if request.cookies:
            return func(*args, **kwargs)
        else:
            # 否則重定向到登入頁面
            return redirect("/login/")
    return wrapper

@app.route("/",endpoint="index")
@check_login_status
def index():
    return "首頁"

print(index.__name__)

@app.route("/news/",endpoint="news")
@check_login_status
def news():
    return "新聞界面"
           

但是執行時,卻發生了如下錯誤:

AssertionError: View function mapping is overwriting an existing endpoint function: wrapper

問題思考

這是一個新手經常會遇到的錯誤,如果不小心把視圖函數寫重名,就會出現這個錯誤,如下所示:

BUG 小記:Flask使用自定義裝飾器問題描述問題思考解決方案參考文檔

但是這裡視圖函數名并沒有問題,為什麼也出現了這個問題?

一頓查閱資料後得知,是 裝飾器 出現了問題!

經過 裝飾器 裝飾之後的函數,它們的

__name__

已經從原來的函數名變成

wrapper

,也就是變成了裝飾器内部的函數名稱

我們可以通過列印函數的

__name__

看到這一結果:

>>> @app.route("/")
>>> @check_login_status
>>> def index():
...    return "首頁"

>>> print(index.__name__)

wrapper
           

解決方案

知道了出錯的原因,我們就可以對症下藥了,具體的解決辦法有如下幾種:

(1) 把每個

wrapper.__name__

設定成唯一的

def check_login_status(func):
    def wrapper(*args, **kwargs):
        if request.cookies:
            return func(*args, **kwargs)
        else:
            # 否則重定向到登入頁面
            return redirect("/login/")
    wrapper.__name__ = func.__name__
    return wrapper
           

(2) 使用

functools.wraps

裝飾下

wrapper

函數

其實這個辦法做的事情和上一個辦法是差不多的,隻不過除了

__name__

之外,它還把

__module__

__doc__

__dict__

都複制到

wrapper

上去了

from functools import wraps
def check_login_status(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # 如果擷取到 session , 即判定為 已登入
        if request.cookies:
            return func(*args, **kwargs)
        else:
            # 否則重定向到登入頁面
            return redirect("/login/")
    return wrapper
           

(3) 顯式設定每個視圖函數的 endpoint 名稱

@app.route("/",endpoint="index")
@check_login_status
def index():
    return "首頁"

@app.route("/news/",endpoint="news")
@check_login_status
def news():
    return "新聞界面"
           

參考文檔

  • 視圖裝飾器 — Flask 中文文檔
  • 實作Flask中view函數的裝飾器
  • 裝飾器 - 廖雪峰的官方網站