Flask之endpoint錯誤View function mapping is overwriting an existing endpoint function: ***


 

最近在學習Flask, 其中遇到了一個錯誤, 發現這個問題和Flask, 路由有關系, 所以就記了下來

 

錯誤代碼:

from flask import Flask, render_template, request, redirect, session

app = Flask(__name__)
app.secret_key = "wang"


def confirm(func):  # 負責確認用戶有沒有登陸的裝飾器
    def inner(*args, **kwargs):
        if session.get("auth"):  # 判斷用戶的session中沒有user
            return func(*args, **kwargs)  # 通過
        else:  # 跳轉登陸頁面, 並攜帶當前訪問的url
            next_url = request.path
            return redirect(f'/login?next={next_url}')

    return inner


@app.route('/')
@confirm
def index():
    return "index"


@app.route('/login', methods=["GET", "POST"])
def login():
    msg = ''
    if request.method == "POST":
        auth = request.form.get('auth')
        if auth == 'wang':  # 簡單認證
            session['auth'] = auth  # 設置session
            next_url = request.args.get('next_url', "/")  # 獲取用戶之前訪問的url, 進行跳轉
            return redirect(next_url)
        else:
            msg = "口令錯誤"
    return render_template("login.html", msg=msg)


@app.route('/shopping')
@confirm
def shopping():
    return "購物"


if __name__ == '__main__':
    app.run(debug=True)

 

報錯:

詭異的是, 我不啟動flask, 只是解釋一遍, 也會報錯

 

報錯分析

分析報錯提示

根據報錯的提示, 說我的代碼存在重復的函數, 然后就開始檢查我的函數, 發現函數名並沒有重復, 難道就這樣排除函數名的嫌疑嗎? NONONO

可能是我對裝飾器的理解還不夠, 找了好半天才發現這個問題, 原來是裝飾器的原因, 為什么呢?

 

為什么說是因為裝飾器, 才會出現函數覆蓋的問題?

再來溫習一下裝飾器

def test(func):  # 裝飾器
    """
    test
    :param func: 其實就是要裝飾的函數
    :return:
    """

    def inner(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        return end - start

    return inner


def outer(a, b):  # 被裝飾的函數
    for n in range(a):
        for j in range(b):
            a = n + j


outer = test(outer)  # 這里因為使用語法糖, 這種方式更能表示出問題
# 在這一步可以說對outer進行了重新的賦值,
# 現在outer就等於test這個函數的返回值, 並且將原本的outer傳了進去
# test函數的返回值是一個inner
# 在inner函數中就包括了原本的outer, 並且這個outer在inner函數中是加了括號的
# 也就是說, 當inner被調用的時候, 原本的outer也會被調用
# 剛剛說test函數返回的是inner函數
# 當outer = test(outer)執行完之后, 新的outer就等於inner了
# 到這只需要知道現在的outer一樣不是原來的outer了, 而是指向了inner, 在inner內部調用原來的outer


print(outer(100000, 200))  # 這是調用函數, 不能改變這個調用方式

 

再來看flask中app.route中的源碼

 flask使用裝飾器來綁定一個url和視圖的關系, 帶着遇到的問題來看看源碼中做了些什么

@app.route('/shopping')     ①
@confirm
def shopping():
    return "購物"

def route(self, rule, **options):
    def decorator(f):
        endpoint = options.pop('endpoint', None)  # ② 從參數中彈出endpoint, 沒有的話就是None
        self.add_url_rule(rule, endpoint, f, **options) #
        return f
        
@setupmethod
    def add_url_rule(self, rule, endpoint=None, view_func=None,
                     provide_automatic_options=None, **options):
                     
        if endpoint is None:  # ④ endpoint如果為None
            endpoint = _endpoint_from_view_func(view_func)  # ⑤ 將試圖視圖函數傳了進去, 返回視圖函數的__name__

def _endpoint_from_view_func(view_func):
    assert view_func is not None, 'expected view func if endpoint is not provided.'
    ~~~~ 其實執行的就是 inner.__name__, 因為此時的shopping, 已經不是原來的shopping, 而是裝飾器內部返回的函數
    return view_func.__name__  # ⑥ 因為沒有定義endpoint, 所以在返回視圖函數的名字

看上面的代碼應該就知道是哪里重復了, 原因就是, 都是調用了inner.__name__, 所以拿到的endpoint值都是一樣的才會提示重復了函數名

就是源碼中的這一段拋出的異常

        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError('View function mapping is overwriting an ' 'existing endpoint function: %s' % endpoint)
            self.view_functions[endpoint] = view_func

 

如何解決這個問題

方法一

根據源碼可以看看出是因為, endpoint重復才導致的, 當endpoint為None時, 就會調用inner.__name__, 就會導致重復, 那么我們不讓endpoint為空, 那就不會調用inner.__name__了

也就不會出現重復的問題了.

現在來看看在哪里可以定義這個endpoint.

還記得源碼中是從哪里去endpoint的嗎, 是不是下面這里

endpoint = options.pop('endpoint', None)

是從option中pop出去的, option就是route(self, rule, **options)的一個形參, 也就是說你在使用app.route的時候可以傳一個endpoint的鍵值對

只需要對兩個視圖的route裝飾器添加一個參數即可, 看代碼吧, 紅色部分是新添加的

@app.route('/', endpoint="index")
@confirm
def index():
    return "index"

@app.route('/shopping', endpoint="shopping")
@confirm
def shopping():
    return "購物"

這樣既可以了, 不信你試試

 

方法二

使用functools.wraps

不太推薦這種方法, 因為flask本身的不穩定性, 所以要盡可能的少用第三方的模塊, 下面只提供了代碼

def confirm(func, *args, **kwargs):
    @wraps(func) def inner():
        if session.get('user'):
            return func(*args, **kwargs)
        else:
            next_url= request.path
            return redirect('/login?next=%s' % (next_url,))

    return inner

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM