zoukankan      html  css  js  c++  java
  • Python学习笔记12:函数修饰符的应用

    Python学习笔记12:函数修饰符的应用

    上一篇笔记Python学习笔记11:函数修饰符介绍了如何构建自己的函数修饰符,这篇笔记通过使用函数修饰符改进web应用来演示如何在实际使用中运用。

    用函数修饰符改进web应用

    添加注册和登录功能

    我们先给web应用添加一个很常见的功能:注册和登录。

    先新建一个用户表:

    CREATE TABLE `myweb`.`user`( `id` INT NOT NULL AUTO_INCREMENT COMMENT '用户id', `name` VARCHAR(20) NOT NULL COMMENT '用户名', `password` VARCHAR(30) NOT NULL COMMENT '密码', `ctime` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ); 
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6jHaqHLn-1616572175066)(https://i.loli.net/2021/03/24/y4iKscp6vdIMzuJ.png)]

    先来添加一个注册页面:

    @web.route("/showRegist", methods=["GET"])
    def showRegist():
        '''用户注册页面'''
        return render_template("regist.html")
    

    相应的模板文件regist.html的代码这里就不展示了,本文末尾会附上所有工程文件,感兴趣的自行下载。

    再加入一个页面用于表单提交注册信息:

    @web.route("/doRegist", methods=["POST"])
    def doRegist():
        '''注册用户'''
        name = request.form['name']
        password = request.form['password']
        redirectUrl = ''
        with MyDB2() as cursor:
            _SQL = '''SELECT * FROM user
    WHERE name=%s LIMIT 1'''
            cursor.execute(_SQL, (name,))
            users = cursor.fetchall()
            if len(users) > 0:
                # 已存在同名用户
                redirectUrl = "/showError/already has same user/1"
                # redirect("/showError/already has same user/1")
            else:
                # 注册用户
                _SQL = '''INSERT INTO `myweb`.`user` (`name`, `password`) VALUES (%s, %s); '''
                params = (name, password)
                cursor.execute(_SQL, params)
                # redirect('/')
                redirectUrl = '/'
        if redirectUrl != '':
            return redirect(redirectUrl)
    

    我们在注册逻辑中加入了重名检查,所以需要再建立一个错误信息显示页面:

    @web.route("/showError/<msg>/<type>", methods=["GET"])
    def showError(msg, type):
        '''错误显示页面'''
        if type == 1:
            url = '/showRegist'
        else:
            url = '/showRegist'
        return render_template('error.html', msg=msg, url=url)
    

    错误信息显示页面需要接收错误信息等参数,关于flask如何接收url参数,可以阅读这里

    现在可以试试新加入的注册页面:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KeNE0crU-1616572175070)(https://i.loli.net/2021/03/24/FwNZyisrmpDKPeY.png)]

    点击后如果没有重名,就会注册用户并跳转到其它页面,我们再看数据库:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zzeySymm-1616572175073)(https://i.loli.net/2021/03/24/6vVqaPY4Nwdc5yK.png)]

    用户添加成功。

    这里的演示相当粗糙(我并不是指页面中英文混杂),我是说这里有很大的安全问题:

    • 密码输入框没有用*掩盖用户输入的密码。
    • 缺少重复输入密码验证。
    • 密码以明文方式存储在数据库。

    这些在实际使用中是不可能接受的,这里只用于演示。

    我们再用类似的方式建立用户登录页面:

    @web.route("/login", methods=["GET"])
    def login():
        '''登录页面'''
        return render_template('login.html')
    
    
    @web.route('/doLogin', methods=["POST"])
    def doLogin():
        '''执行登录逻辑'''
        name = request.form['name']
        password = request.form['password']
        if checkUserPassword(name,password)==False:
            return redirect('/showError/password is error/2')
        return redirect('/')
    
    
    def checkUserPassword(user, password):
        with MyDB2() as cursor:
            _SQL = '''SELECT * FROM USER
    WHERE NAME=%s LIMIT 1;'''
            params = (user,)
            cursor.execute(_SQL, params)
            users = cursor.fetchall()
        if len(users) == 0:
            return False
        realPassword = users[0][2]
        if password != realPassword:
            return False
        return True
    

    现在我们的登录注册逻辑都有了,但是仅仅加上了两个功能而已,并不能实用化,因为没有使用session进行状态保持。

    使用session进行状态保持

    在flask框架中使用session很简单:

    from flask import Flask, render_template, request, redirect, session
    from mydb2 import MyDB2
    web = Flask(__name__)
    web.secret_key="sfdfjk@.sfdsf"
    

    只需要两步:

    1. 从flask模块导入session。
    2. 给flask实例设置一个用于给session加密的secret_key

    我们先要在登录逻辑中写入session:

    @web.route('/doLogin', methods=["POST"])
    def doLogin():
        '''执行登录逻辑'''
        name = request.form['name']
        password = request.form['password']
        checkResult = checkUserPassword(name, password)
        if not checkUserPassword:
            return redirect('/showError/password is error/2')
        # 登录成功,写入session
        session['uid'] = checkResult['uid']
        session['name'] = checkResult['name']
        return redirect('/')
    

    然后写一个函数根据session来判断是否已经登录:

    def isLogged():
        '''判断当前是否登录状态'''
        if not 'uid' in session:
            return False
        uid = int(session['uid'])
        if uid<=0:
            return False
        return True
    

    OK,现在我们只需要给登录后才能看的页面加上登录检查的逻辑:

    @web.route('/log')
    def showLog() -> 'html':
        """展示日志页面"""
        if not isLogged():
            return redirect('/login')
        logList = getLog()
        return render_template("log.html", the_title="服务器日志", log_list=logList)
    

    好像挺简单的,我们这里只是一个日志页需要访问控制,但如果有很多页面呢?难道要一个一个复制粘贴if语句吗?

    当然不是,该是我们的函数修饰符闪亮登场的时候了。

    使用函数修饰符改善既有代码

    我们新建一个login_check.py用于编写函数修饰符:

    from flask import session, redirect
    from functools import wraps
    
    
    def isLogged():
        '''判断当前是否登录状态'''
        if not 'uid' in session:
            return False
        uid = int(session['uid'])
        if uid <= 0:
            return False
        return True
    
    
    def loginCheck(webRoute):
        '''函数修饰符,用于给webroute增加登录检查'''
        @wraps(webRoute)
        def loginCheckWebRoute(*params, **kvParams):
            if not isLogged():
                return redirect('/login')
            return webRoute(*params, **kvParams)
        return loginCheckWebRoute
    

    这并不难,我们的函数修饰符只是经过三个步骤:

    1. 接收一个web应用的页面路由函数。
    2. 创建一个内部函数,在内部函数中实现先检查登录状态,如果已登录,就执行路由函数并返回其结果。
    3. 将内部函数作为返回值返回。

    好了,现在我们可以在我们的主文件index.py中应用这个函数修饰符了。

    @web.route('/log')
    @loginCheck
    def showLog() -> 'html':
        """展示日志页面"""
        logList = getLog()
        return render_template("log.html", the_title="服务器日志", log_list=logList)
    

    在删除不必要的代码后,只需要在路由函数前面加入@loginCheck就可以实现在进入页面逻辑前先检查登录状态。

    当然,你也可以照例给其它想要访问控制的页面加入登录检查。

    现在我们应该对函数修饰符的优点有所感悟:

    • 减少了重复代码的使用。

      当然,你也可以用函数包装重复代码后再调用,但无疑函数修饰符的写法更简洁

    • 在没有改变原有业务代码的情况下增强了代码行为,这可以让你的代码更简洁明了,将业务代码和基础性的功能代码剥离。

      如果你有商业开发经验,你会对这一点深有体会。

    好了,我们现在已经讲完了如何在实际工作中使用函数修饰符。我们现在可以讨论一些其它的关于函数修饰符的主题。

    在这里附上目前web应用的全部工程代码:

    链接:https://pan.baidu.com/s/1fm18rgWKYy_HbN9ugtREiw
    提取码:nqrz
    复制这段内容后打开百度网盘手机App,操作更方便哦

    函数修饰符与设计模式

    Python学习笔记11:函数修饰符的末尾我说过,函数修饰符这种称呼很别扭,老实说,我第一次看到的时候以为@这个符号是函数修饰符,后来才知道它只是用来标记函数修饰符。

    在我看来,其实称呼为函数修饰器更为妥当,对设计模式有所了解的朋友应该知道,设计模式中有种叫做修饰器或者说装饰器模式。其核心思想就是通过一系列技术对已有类进行封装,以实现增强其功能的目的。而这和Python中的函数修饰器的用途别无二致。

    在理解设计模式的时候,我们要时刻保持这样一种认识:设计模式只是一种思想,是为了解决某一类相似问题总结出的解决方案。而且各种设计模式之前只有明显的倾向,而并没有明显的分界线

    比如装饰器和适配器,当初我阅读设计模式的时候相当费解,傻傻分不清。后来我偶然注意到电源适配器,才茅塞顿开。

    适配器,顾名思义,其侧重点在于适配不同的部件,比如笔记本的电源适配器,如果没有,你直插220V高压电插座,那你笔记本立即阵亡,这时候,就需要一个适配器来对电源插座和笔记本做适配,充当之间的桥梁,让你笔记本安全使用到低压直流电。

    而装饰器的侧重点在增强已有物品的功能,比如前文举的例子,给坦克加装反应装甲。

    当然,你完全可以通过“魔改”装饰器来起到适配器的作用,比如我找一个某电工大神,给笔记本电脑焊装一个扩展,可以直接接高压直流电的那种。也可以实现目的,但是其可扩展性和灵活性就低很多了。

    所以你看,重要的并不是其实现细节和能否互相替代,而是在合适的场景选取合适的设计模式

    现在我们说回来,这里讲的函数修饰符和Python学习笔记10:上下文协议中说的上下文协议,是两种设计模式,他们的侧重点不同,上下文协议更倾向于让某段代码实现自动的上下文切换。

    而如同之前我们讨论的,我们完全可以用函数修饰符来尝试实现上下文协议,虽然这种“魔改”行为没有多少实际价值,但对我们理解Python本身还是一个不错的体验,更何况比较有趣。

    from functools import wraps
    def wrapContent(contentFunc):
        @wraps(contentFunc)
        def wrapContentFunc(*params,**vkParams):
            print("this is a before action")
            beforeReturn = [1,2,3]
            vkParams['beforeReturn'] = beforeReturn
            contentFunc(*params,**vkParams)
            print("this is a after action")
        return wrapContentFunc
    
    @wrapContent
    def contentFunc(beforeReturn):
        print("this is a content")
        print(beforeReturn)
    
    contentFunc()
    

    输出

    this is a before action
    this is a content
    [1, 2, 3]
    this is a after action

    这里最难的部分是上下文切换的时候会传给业务代码段一个句柄,这里我通过在内部函数中给可变参数vkParams追加一个参数的方式实现句柄传递。

    好了,关于函数修饰符的讨论告一段落,感谢阅读。

    本篇文章首发自魔芋红茶的博客https://www.cnblogs.com/Moon-Face/ 请尊重其他人的劳动成功,转载请注明。
  • 相关阅读:
    Python if语句
    Pyhton数据类型总结
    Flask系列之自定义中间件
    Flask系列之蓝图中使用动态URL前缀
    python+Nginx+uWSGI使用说明
    python之threading.local
    python之偏函数
    Flask系列之源码分析(一)
    Python扩展之类的魔术方法
    Flask系列(十一)整合Flask中的目录结构(sqlalchemy-utils)
  • 原文地址:https://www.cnblogs.com/Moon-Face/p/14582310.html
Copyright © 2011-2022 走看看