python - Flask:为什么 app.route() 装饰器应该总是在最外面?

标签 python flask decorator python-decorators

假设,我有一个手工制作的 @login-required 装饰器:

from functools import wraps

def login_required(decorated_function):
    """Decorator to check if user is logged in."""

        @wraps(decorated_function)
        def wrapper(*args, **kwargs):
        if False: # just to check it's working
            return decorated_function(*args, **kwargs)
        else:
            flash('You need to login, to access this page')
            return redirect(url_for('login'))
    return wrapper 

和一个函数,用 @app.route()@login_required 装饰(为简洁起见,省略了 login 的端点):

@app.route('/')
@login_required
def index():
    return "Hello!"

现在,如果我尝试访问 /,正如预期的那样,它不会让我访问,并且会重定向到登录页面。
不过,如果我滑动装饰器的顺序,即:

@login_required
@app.route('/')
def index():
    return "Hello!"

然后我就可以访问 /,即使我不应该这样做。

我知道Flask documentation on the subject状态:

When applying further decorators, always remember that the route() decorator is the outermost.

我还看到了other questions在同一问题上。

<小时/>

我好奇的不是正确的方法是什么(@app.route() 装饰器必须在最外面 - 明白了),但是而是为什么它会这样工作(即它背后的机制是什么)。

我查看了@app.route() source code :

   def route(self, rule, **options):
    def decorator(f):
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator

This answer ,或多或少帮助我理解了装饰器的机制。不过,我以前从未见过函数只是返回(没有调用它),所以我自己做了一个小实验(当然,结果是可行的):

def my_decorator():
    def decorator (function):
        return function
    return decorator

@my_decorator()
def test():
    print('Hi')

test()

所以,我想了解:

  • 为什么装饰器的顺序在上面的具体情况下以及对于 @app.route() 和其他装饰器来说很重要(我猜这是相同的答案)?让我困惑的是,@app.route() 只是向应用程序添加了 url 规则(即 self.add_url_rule(rule, endpoint, f, **options) 和返回函数,就是这样,为什么顺序很重要?
  • @app.route() 是否会覆盖其上方的所有装饰器(如果是的话,如何覆盖)?

我也是aware ,装饰器应用顺序是从下到上,尽管对我来说这并没有让事情变得更清晰。我错过了什么?

最佳答案

你自己已经差不多解释清楚了! :-) app.route 确实

self.add_url_rule(rule, endpoint, f, **options)

但关键是这里的 f 是修饰的任何函数。如果您首先应用 app.route,它会为原始函数添加 URL 规则(不带登录装饰器)。登录装饰器包装了该函数,但 app.route 已经存储了原始未包装的版本,因此包装无效。

设想“展开”装饰器可能会有所帮助。想象一下你是这样做的:

# plain function
def index():
    return "Hello!"

login_wrapped = login_required(index)         # login decorator
both_wrapped = app.route('/')(login_wrapped)  # route decorator

这是“正确”的方式,首先发生登录换行,然后发生路由。在此版本中,app.route 看到的函数已使用登录包装器进行了包装。错误的做法是:

# plain function
def index():
    return "Hello!"

route_wrapped = app.route('/')(index)        # route decorator
both_wrapped = login_wrapped(route_wrapped)  # login decorator

在这里你可以看到 app.route 看到的只是普通的未包装版本。该函数稍后用登录装饰器包装的事实没有任何影响,因为那时路由装饰器已经完成。

关于python - Flask:为什么 app.route() 装饰器应该总是在最外面?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47467658/

相关文章:

python - "No installed app with label ' 管理员 '"运行 Django 迁移。该应用程序已正确安装

python - 使用 web2py 的异步后台进程

python - 使用 Beautiful Soup 将 CSS 属性转换为单个 HTML 属性?

python - 捕获通过 gunicorn 运行的 python 标准输出

c++ - 装饰器设计模式,函数bug

python - 如何从sqlite中读取日期时间作为日期时间而不是Python中的字符串?

flask - 如何使用 Flask-Admin 向索引页添加内容

python - PUT curl 请求返回错误的 URI (flask-RESTful)

python - 如何向现有对象方法添加装饰器?

python - 在装饰器中修改文档字符串