6

我正在创建一个 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'))

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

4

1 回答 1

13

这里的问题是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

更多解释:在 Python 中,装饰器是一个函数,它接受另一个函数作为其参数并返回一个函数作为其结果。所以以下

@logged_in
def index(): pass

本质上等同于

def index(): pass
index = logged_in(index)

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

decorator.__name__ = fn.__name__

或更好地使用模块update_wrapperwraps帮助functools器,它们可以为您做到这一点,甚至更多。

于 2013-01-01T21:57:08.073 回答