问题
Say, I have a hand-crafted @login-required
decorator:
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
and a function, decorated with @app.route()
and @login_required
(endpoint for login
omitted for brevity):
@app.route('/')
@login_required
def index():
return "Hello!"
Now, if I try to access /
, as expected, it won't let me and will redirect to the login page.
Though, if I swipe the the order of the decorators i.e.:
@login_required
@app.route('/')
def index():
return "Hello!"
then I am able to access /
, even though I shouldn't be.
I am aware that Flask documentation on the subject states:
When applying further decorators, always remember that the route() decorator is the outermost.
I have also seen other questions on the same issue.
What I'm curious about is not what is the proper way to do it (@app.route()
decorator must be outermost - got it), but rather why it is working this way (i.e. what is the mechanics behind it).
I took a look at @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, helped me to understand mechanism of decorators, more or less. Though, I have never seen function just returned (without calling it) before, so I did a little experiment myself (which turned out to be workable, of course):
def my_decorator():
def decorator (function):
return function
return decorator
@my_decorator()
def test():
print('Hi')
test()
So, I would like to understand:
- Why order of decorators matter in the exact case above and for
@app.route()
and other decorators in general (which is the same answer, I guess)? What confuses me, is that@app.route()
just adds url rule to the app (i.e.self.add_url_rule(rule, endpoint, f, **options)
and returns the function, that's it, so why would order matter? - Does
@app.route()
overrides all the decorators above it (how if so)?
I am also aware, that decorators application order is from bottom to top, though it doesn't make things any clearer, for me. What am I missing?
回答1:
You have almost explained it yourself! :-) app.route
does
self.add_url_rule(rule, endpoint, f, **options)
But the key is that f
here is whatever function was decorated. If you apply app.route
first, it adds a URL rule for the original function (without the login decorator). The login decorator wraps the function, but app.route
has already stored the original unwrapped version, so the wrapping has no effect.
It may help to envision "unrolling" the decorators. Imagine you did it like this:
# plain function
def index():
return "Hello!"
login_wrapped = login_required(index) # login decorator
both_wrapped = app.route('/')(login_wrapped) # route decorator
This is the "right" way where the login wrap happens first and then the route. In this version, the function that app.route
sees is already wrapped with the login wrapper. The wrong way is:
# plain function
def index():
return "Hello!"
route_wrapped = app.route('/')(index) # route decorator
both_wrapped = login_wrapped(route_wrapped) # login decorator
Here you can see that what app.route
sees is only the plain unwrapped version. The fact that the function is later wrapped with the login decorator has no effect, because by that time the route decorator has already finished.
来源:https://stackoverflow.com/questions/47467658/flask-why-app-route-decorator-should-always-be-the-outermost