• Flask框架视图多层装饰器问题


    Flask中的app.route装饰器

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

    from flask import Flask, render_template, redirect, request, session
    ​
    app = Flask(__name__)
    app.debug = True
    app.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")
    @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!

     

  • 相关阅读:
    python实现七段数码管显示
    词频统计实例
    分形几何中科赫雪花的绘制
    脚本实现自动化绘制
    Android查看数据库方法及工具(转)
    2011年度总结
    Bad NPObject as private data 解决方案
    LINQ学习之旅——LINQ TO SQL继承支持(转载)
    我记录开源系统1.6源码解析(一)
    2011年度总结
  • 原文地址:https://www.cnblogs.com/ryxiong-blog/p/11170586.html
Copyright © 2020-2023  润新知