【问题标题】:Flask: why app.route() decorator, should always be the outermost?Flask:为什么 app.route() 装饰器,应该总是在最外面?
【发布时间】:2018-05-08 03:17:44
【问题描述】:

说,我有一个手工制作的@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 表示:

在应用更多装饰器时,请始终记住 route() 装饰器位于最外层。

我也看到过otherquestions 在同一个问题上。


我好奇的不是正确的方法是什么(@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,装饰器的应用顺序是从下到上的,虽然对我来说并没有让事情变得更清楚。我错过了什么?

【问题讨论】:

    标签: python flask decorator python-decorators


    【解决方案1】:

    你几乎已经自己解释过了! :-) 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 看到的只是简单的展开版本。函数稍后用登录装饰器包装的事实没有任何效果,因为到那时路由装饰器已经完成。

    【讨论】:

    • 感谢您提供如此全面的解释。有趣的是:在发布问题之后——我浏览了this article,它基本上解释了如何编写自己的@app.route()因此,@app.route() 如何在 Flask 中工作),以及我自己理解 =)。
    猜你喜欢
    • 2020-07-24
    • 2018-02-03
    • 2012-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-29
    • 2015-01-09
    相关资源
    最近更新 更多