为什么Flask的url_for在Python中对该项使用装饰器时会抛出错误?

chr*_*ude 5 python decorator build-error flask

我正在创建一个Python Flask应用程序,并在下面创建了装饰器和视图.装饰器在查看索引时效果很好,但是当您注销并使用url_for索引重定向时,它会抛出一个构造函数.为什么会

def logged_in(fn):
    def decorator():
        if 'email' in session:
            return fn()
        else:
            return render_template('not-allowed.html', page="index")
    return decorator


@app.route('/')
@logged_in
def index():
    email = session['email']    
    return render_template('index.html', auth=True, page="index", marks=marks)

@app.route('/sign-out')
def sign_out():
    session.pop('email')
    print(url_for('index'))
    return redirect(url_for('index'))
Run Code Online (Sandbox Code Playgroud)

有任何想法吗?错误是:BuildError: ('index', {}, None)

Aud*_*kas 12

这里的问题是decorator()您返回的函数与其正在装饰的函数具有不同的名称,因此URL构建器无法找到您的index视图.您需要使用模块中的wraps()装饰器functools来复制原始函数的名称.另一个问题(您仍然需要遇到)是您不接受装饰器中的参数并将其传递给原始函数.这是纠正的装饰者:

from functools import wraps

def logged_in(fn):
    @wraps(fn)
    def decorator(*args, **kwargs):
        if 'email' in session:
            return fn(*args, **kwargs)
        else:
            # IMO it's nicer to abort here and handle it in errorhandler.
            abort(401)
    return decorator
Run Code Online (Sandbox Code Playgroud)

更多解释:在Python中,decorator是一个函数,它将另一个函数作为其参数并返回一个函数作为其结果.所以以下

@logged_in
def index(): pass
Run Code Online (Sandbox Code Playgroud)

基本上是相同的

def index(): pass
index = logged_in(index)
Run Code Online (Sandbox Code Playgroud)

在这种情况下的问题是你的logged_in装饰器返回的不是原始函数,而是包装原始函数的包装器(decorator在代码中调用).此包装器具有decorator与其包装的原始函数不同的名称().现在app.route(),您调用的decorator会logged_in看到这个新函数并使用其name(decorator)为它注册一个路径.问题在于:您希望装饰函数具有相同的名称(index),因此可以用它url_for()来获取它的路径.这就是您需要手动复制名称的原因

decorator.__name__ = fn.__name__
Run Code Online (Sandbox Code Playgroud)

或者更好地使用update_wrapper和模块中的wraps帮助程序functools,这样做甚至更多.

  • 你能解释一下"不同签名"意味着什么吗? (3认同)