博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Flask框架视图多层装饰器问题
阅读量:5119 次
发布时间:2019-06-13

本文共 3839 字,大约阅读时间需要 12 分钟。

Flask中的app.route装饰器

我们知道,在flask框架中,我们的路由匹配就是通过有参装饰器来实现的,我们看一个简单的例子:

from flask import Flask, render_template, redirect, request, session​app = Flask(__name__)app.debug = Trueapp.secret_key = '123'​@app.route('/index')def index():    return "这是主页"@app.route("/detail")def detail():  return "这是详情页面"if __name__ == '__main__':    app.run()

这个装饰器我们见怪不怪,很明显是可以正常运行的;

Flask视图的多个装饰器

现在需求来了,我们需要给某些视图函数添加一个装饰器,用来验证用户是否是登录的状态,只有登录的用户才能访问,否则就让他跳转登录页面去登录。

很多小伙伴很机智,很快就想到了装饰器的嵌套,对的,我们可以用多个装饰器来装饰,既然这样,问题又来了,装饰器是写在route装饰器上方呢,还是下方呢?

1.源码分析

这里我们截取一点route源码

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

我们看route的源码,不难发现其实route方法也是返回了一个装饰器,然后用来装饰我们的视图函数,并且在add_url_rule中匹配了访问路径,以及处理视图函数的逻辑;

也就是说,route装饰器应该是最后来处理视图函数的逻辑的,那么很明显,route应该装饰在视图函数的外层,装饰被其他装饰器装饰过后返回的视图函数。

2.装饰器实践

有了思路后,一顿操作我把多层装饰器写出来了,代码如下

from flask import Flask, request, render_template, session, redirect​app = Flask(__name__)app.debug = True​def wrapper(func):    """验证登录状态装饰器函数"""    def inner(*args,**kwargs):        if session.get("username"):            # 如果session中有用户信息,正常执行            ret = func(*args, **kwargs)            return ret        else:            return redirect("/login")    return inner​@app.route("/login",methods=["get","post"])def login():    if request.method == "GET":        return render_template("login.html")    else:        username = request.form.get("username")        password = request.form.get("password")        if username == "123" and password == "123":            session["username"] = username            return "登录成功!"​        else:            return "登录失败!"​​@app.route("/index")@wrapperdef index():    return "这是主页"​​@app.route("/detail")@wrapperdef detail():    return "这是详情页面"​if __name__ == '__main__':    app.run()

激动的启动了我的项目,一秒都不用,啪,报错了。~~~

这是什么鬼!我们仔细看一下报错信息,视图函数映射被一个存在的末端函数inner覆盖写入了。

问题分析

报错说视图函数被一个存在的函数inner覆盖写入了,我们想一想,哪里用了inner啊,不就是装饰函数内的inner函数吗?

我们大概看一下源码,这里我截取的一部分

if endpoint is None:    endpoint = _endpoint_from_view_func(view_func)    options["endpoint"] = endpoint    methods = options.pop("methods", None)    if view_func is not None:    old_func = self.view_functions.get(endpoint)    if old_func is not None and old_func != view_func:        raise AssertionError(            "View function mapping is overwriting an "            "existing endpoint function: %s" % endpoint        )    self.view_functions[endpoint] = view_func

肯定有很多小伙伴看到这里,已经晕了,这里我就大概解释一下:

endpoint是我们传递的一个用来给视图函数去别名的变量,我们不传的情况下,默认是None,如果是None,那么endpoint会调用一个方法_endpoint_from_view_func去获取被装饰的函数的名字,因为我们的route装饰的是被wrapper装饰后返回的inner函数。

第一次在装饰index视图的时候,endpoint被赋值为inner,添加到self.view_functions中,第二次装饰detail的时候,route又装饰了第一次装饰过detail返回的inner。注意:两个inner名字是一样的,但是内容不一样。问题就在这里了,route装饰inner的时候,发现有两个inner,名字一样,内容不一样,这个时候route就不知道改装饰哪一个了,因为第二个inner会覆盖view_functions中第一次存入的inner函数,就会出现刚才的报错。

问题解决方法

问题的原因我们发现了,本质上就是wrapper两次装饰了不同视图函数,但是返回的都是inner函数,导致route装饰的时候出现覆盖的情况才报错。

我们只要让两次装饰后的inner函数名不一样就可以了,对不对!

方法一:endpoint

我们看过了源码,名字冲突的原因是因为每次都是去获取inner的__name__,很明显会冲突,但是去获取名字的前提是因为我们没有传递endpoint别名,这样就好办了。

我们在使用route装饰的时候加上别名就轻松解决了。

@app.route("/index",endpoint="index")@wrapperdef index():    return "这是主页"​​@app.route("/detail",endpoint="detail")@wrapperdef detail():    return "这是详情页面"

修改后运行,问题迎刃而解,so easy啊!

方法二:wraps工具

同样的问题,既然是名字冲突,那么我们是不是可以想到functiontool中的wraps工具呢,就是让内层函数使用原本函数的属性字典__dict__,也包括名字啦。

我们在写wrapper装饰器的时候使用上

from functools import wraps​def wrapper(func):    """验证登录状态装饰器函数"""    @wraps(func)    def inner(*args,**kwargs):        if session.get("username"):            # 如果session中有用户信息,正常执行            ret = func(*args, **kwargs)            return ret        else:            return redirect("/login")    return inner

修改运行,同样完美结局,excellent!

 

转载于:https://www.cnblogs.com/ryxiong-blog/p/11170586.html

你可能感兴趣的文章
Day19内容回顾
查看>>
第七次作业
查看>>
SpringBoot项目打包
查看>>
Linux操作系统 和 Windows操作系统 的区别
查看>>
《QQ欢乐斗地主》山寨版
查看>>
文件流的使用以及序列化和反序列化的方法使用
查看>>
Android-多线程AsyncTask
查看>>
第一个Spring冲刺周期团队进展报告
查看>>
红黑树 c++ 实现
查看>>
Android 获取网络链接类型
查看>>
linux中启动与终止lnmp的脚本
查看>>
gdb中信号的处理[转]
查看>>
LeetCode【709. 转换成小写字母】
查看>>
如何在Access2007中使用日期类型查询数据
查看>>
Jzoj4757 树上摩托
查看>>
CF992E Nastya and King-Shamans(线段树二分+思维)
查看>>
第一个Java Web程序
查看>>
树状数组_一维
查看>>
如果没有按照正常的先装iis后装.net的顺序,可以使用此命令重新注册一下:
查看>>
linux install ftp server
查看>>