Flask中的app.route装饰器

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

from flask import Flask, render_template, redirect, request, session

app = Flask(__name__)
app.debug = True
app.secret_key = ''

@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 == "" and password == "":
session["username"] = username
return "登录成功!"

else:
return "登录失败!"


@app.route("/index")
@wrapper
def index():
return "这是主页"


@app.route("/detail")
@wrapper
def 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")
@wrapper
def index():
return "这是主页"


@app.route("/detail",endpoint="detail")
@wrapper
def 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!

Flask框架视图多层装饰器问题的更多相关文章

  1. flask笔记(三)Flask 添加登陆验证装饰器报错,及解析

    Flask 添加登陆验证装饰器报错,及解析 写这个之前,是想到一个需求,这个是关于之前写Flask笔记(二)中的一个知识点,路由相关 需求为 : 有一些页面必须是登陆之后才能访问的,比如Shoppin ...

  2. python 多层装饰器

    25.多层装饰器:      1.原理:执行顺序从上往下,#2和#3组成一个函数假设为nf1,#1和nf1组成一个函数nnf1           f1成为ck_ty_of_us的inner函数即nf ...

  3. Django中类视图使用装饰器的方式

    类视图使用装饰器 为类视图添加装饰器,可以使用两种方法. 为了理解方便,我们先来定义一个为函数视图准备的装饰器(在设计装饰器时基本都以函数视图作为考虑的被装饰对象),及一个要被装饰的类视图. def ...

  4. python-day5-装饰器第二弹之多层装饰器

    多层装饰器 #首先我们先实现一个简单的登陆与权限验证功能,注意看执行结果 USER_INFO = {} def check_login(func): def inner(*args,**kwargs) ...

  5. Django---CBV和FBV的使用,CBV的流程,给视图加装饰器,Request对象方法,属性和Response对象,form表单的上传

    Django---CBV和FBV的使用,CBV的流程,给视图加装饰器,Request请求对象方法,属性和Response响应对象,form表单的上传 一丶CBV和FBV       在Django中存 ...

  6. Django:(05)类视图,装饰器和中间件

    一.类视图的定义和使用 在Django中还可以通过类来定义一个视图,称为类视图. 定义一个类视图:定义一个类,需继承 Django 提供的 View 类 . from django.views.gen ...

  7. python 装饰器 第十一步:多层装饰器的嵌套

    #第十一步:多层装饰器的嵌套 #装饰器1 def kuozhan1(func): #定义装饰之后的函数 def neweat1(): # 扩展功能1 print('1-----饭前洗手') # 调用基 ...

  8. django类视图的装饰器验证

    django类视图的装饰器验证 django类视图的get和post方法是由View内部调用dispatch方法来分发,最后调用as_view来完成一个视图的流程. 函数视图可以直接使用对应的装饰器 ...

  9. 10、Flask实战第10天:视图使用装饰器

    在实际开发中,我们有时候会用到自己定义装饰器并应用到函数视图或者类视图里面:比如:我们要想进入个人中心页面,首先要验证你是否登录,否则进不去,下面我们来模拟这个场景 定义一个装饰器 from func ...

随机推荐

  1. python控制流-流程控制语句

    一.if语句 if 语句的子句(也就是紧跟 if 语句的语句块), 将在语句的条件为 True 时执行.如果条件为 False,子句将跳过. 在英文中,if 语句念起来可能是“:如果条件为真,执行子句 ...

  2. 一次特殊的“VARCHAR转numeric失败”错误记录

    今天接触到一个很有意思的问题.当我在执行一条INSERT的sql语句时,他总是报字符串转数字类型失败. 问题 首先,该表中的所有数字类型的字段都是非必填,其次,每个数字类型的字段都有默认值.最令我感到 ...

  3. [Web 前端] 019 css 定位之绝对定位与相对定位

    1. 关于定位 我们可以使用 css 的 position 属性来设置元素的定位类型 postion 的设置项如下 设置项 释义 relative 生成相对定位元素元素所占据的文档流的位置不变元素本身 ...

  4. [Python3] 007 列表的遍历,你是 for 联盟还是 while 部落

    目录 少废话,直接上例子 for 联盟 for 与 list 的简单合作 for 的老搭档 range() for 与嵌套列表(双层列表) for 从 if 那儿认识的 else 循环"三杰 ...

  5. [2019杭电多校第四场][hdu6621]K-th Closest Distance(主席树)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6621 题意为求区间[l,r]内第k小|a[i]-p|的值. 可以二分答案,如果二分的值为x,则判断区间 ...

  6. Lost Cows POJ 2182 思维+巧法

    Lost Cows POJ 2182 思维 题意 是说有n头牛,它们身高不一但是排成了一队,从左到右编号为1到n,现在告诉你从第二号开始前面的那些牛中身高小于它的个数,一共有n-1个数.然后求出它们按 ...

  7. Kintex 7五兄弟

    基KC705E 增强版 基于FMC接口的Xilinx Kintex-7 FPGA K7 XC7K325T PCIeX8 接口卡(136) 本板卡是Xilinx公司芯片V5系列芯片设计信号处理板卡.由一 ...

  8. $PMTargetFileDir 参数位置

    系统/session参数与变量参数和变量都配置在Session中,如$PMTargetFileDir.$PMBadFileDir等.这些变量有哪些.在哪里定义.是否可以修改呢?在控制台(Admin C ...

  9. tail 显示文件内容尾部

    1.命令功能 tail默认显示文件内容尾部10行. 2.语法格式 tail  option  file 参数说明 参数 参数说明 -n 显示指定行数 -f 实时输出文件变化后追加的数据 -s 监视文件 ...

  10. Java 静态方法、私有方法、常量的使用

    1.静态方法 2.私有方法 3.常量 4.接口小结