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/ 请尊重其他人的劳动成功,转载请注明。
原文地址:https://www.cnblogs.com/Moon-Face/p/14582310.html