• 装饰器


    一、装饰器基础

    1、什么是装饰器?

      装饰器就是一个返回函数的高阶函数。顾名思义,装饰器就是对现有的函数的装饰,在不改变现有函数的的源代码以及调用方式的基础上添加新的功能。所以,一个装饰器应该满足以下三点原则:

    • 不改变现有函数的源代码(开放封闭原则)
    • 不改变现有函数的调用方式
    • 能够添加满足需求的新功能

    2、实现基础

      如何实现装饰器的三条原则呢?需要使用以下三个知识点:

    • 高阶函数
    • 函数嵌套
    • 闭包

    3、高阶函数

      高阶函数就是变量可以指向函数,并且函数的参数能接收变量,那么一个函数就可以接受另一个函数作为参数,最后返回值可以是函数。所以一个高阶函数满足以下两点之一就是高阶函数。

    • 函数接收的参数是一个函数名
    • .函数的返回值是一个函数名

    如果仅仅通过高阶函数能否满足装饰器的原则呢?假如现在有一个需求,计算一个函数的的运行时间,一般情况下应该这样就可以实现的。

    import time
    
    def Bar():
        start_time=time.time()
        time.sleep(2)
        print("我是bar函数")
        end_time=time.time()
        return end_time-start_time
    
    res=Bar()
    print(res)
    
    ###################输出结果##########
    #我是bar函数
    #2.0001144409179688

    这样是可以实现的,但是如果需要为1000或者更多函数添加此项功能呢?在不违背开放封闭的前提下,只能在原有功能的基础上进行装饰Bar函数。

    #函数接收的参数是一个函数名
    import time
    
    def Bar():
        time.sleep(2)
        print("我是bar函数")
    
    def CalTime(func):
        start_time=time.time()
        func()
        end_time=time.time()
        return end_time-start_time
    
    res=CalTime(Bar)
    print(res)
    ######################输出#############
    #我是bar函数
    #2.0001144409179688

    这里应用高阶函数的第一点,传入函数名,可以看到CalTime函数实现了计算时间功能,并且没有改变Bar函数源代码,但是函数的调用方式发生了变化,通过:

    CalTime(Bar)

    进行调用,所以传入函数名:

    • 可以实现功能
    • 改变了调用方式
    • 源代码未改变

    然后利用高阶函数的第二个特性,返回函数名:

    #函数返回的是一个函数名
    import time
    
    def Bar():
        time.sleep(2)
        print("我是bar函数")
    
    def CalTime(func):
        start_time=time.time()
        func()
        end_time=time.time()
        print( end_time-start_time)
        return func
    
    Bar=CalTime(Bar)
    Bar()
    
    #################输出#########
    #我是bar函数
    #2.0001144409179688
    #我是bar函数

    从上面的输出看好像实现了装饰器的原则呀,不,细看后可以发现,调用方式没有发生变化,但是功能没有实现,它多打印了一行内容,这是由于返回函数名的原因,所以:

    • 没有实现功能
    • 调用方式未发生变化
    • 源代码未改变

    此时,可能有的人想将两个综合起来不就实现了吗?事实上结果是:

    • 没有实现功能
    • 调用方式未发生变化
    • 源代码未改变

    此时明显高阶函数不能完全解决这个问题,引入嵌套函数?

    4、函数嵌套

      python中定义函数的关键字是def,如果在函数中使用def关键字再创建函数,这时就会形成函数嵌套。

    def f1():
        name="bar"
        def f2():
            print(name)
        f2()
    f1() #bar

      像上述这样的形式属于函数嵌套,而函数中调用其它函数不属于函数嵌套,那么引入函数嵌套到底有什么作用呢?从上述的执行结果可以看到打印出name的值,f2作用域中没有就会向上一层中寻找变量,这里就是需要利用这个特性来做文章的。 

    import time
    def Bar():
        time.sleep(2)
        print("我是bar函数")
    
    def CalTime(func):
        def wrapper():
            start_time=time.time()
            func()
            end_time=time.time()
            print(end_time-start_time)
        return wrapper
    
    Bar=CalTime(Bar)
    Bar()
    
    ##############输出####
        #我是bar函数
        #2.0001144409179688

      此时可以看到我在wrapper函数中执行了外层作用域中函数变量func,而且可以看到输出结果是正确的,这样装饰器基本实现了,可能有的人说闭包呢?函数嵌套里面已经包括闭包了,wrapper函数是一个闭包,CalTime函数也是一个闭包,当然这还不是一个完整的装饰器,不可能每一次调用方式都需要这样写吧?

    Bar=CalTime(Bar)
    Bar()

      所以,此时引入“@”

    import time
    
    def CalTime(func):
        def wrapper():
            start_time=time.time()
            func()
            end_time=time.time()
            print(end_time-start_time)
        return wrapper
    
    @CalTime # Bar=CalTime(Bar)
    def Bar():
        time.sleep(2)
        print("我是bar函数")
    Bar()

      这样一个简单的装饰器就出来了

    @CalTime 就相当于 Bar=CalTime(Bar)

      可以整理一下运行过程,Bar()运行相当于CalTime(Bar)(),CalTime(Bar)()相当于执行函数wrapper(),而在wrapper函数中又执行了func函数(也就是Bar())。

    二、装饰器

    1、装饰带参数的函数

      上面是简单的装饰器,装饰的函数是没有参数的,如果Bar()是带有参数的函数,此时又应该如何解决呢?

    import time
    
    def CalTime(func):
        def wrapper(*args,**kwargs):#相当于Bar(1,b=2) args=(1,) kwargs={"b":2}
            start_time=time.time()
            func(*args,**kwargs) #运行Bar() func(*(1,),**{'b':18})
            end_time=time.time()
            print(end_time-start_time)
        return wrapper
    
    @CalTime # Bar=CalTime(Bar)
    def Bar(a,b):
        time.sleep(2)
        print("我是bar函数")
    Bar(1,b=2)
    
    #########################输出################
        #我是bar函数
        #2.0001144409179688

    这就是被修饰的函数加上可变的参数,这样装饰器会更加实用

    2、装饰带返回值的函数

    上面已经说了装饰带参数的函数,但是却没有返回值,可以由上面的代码看到执行wrapper函数后没有任何返回值,可以打印Bar函数的执行结果为None。

    ...
    
    res=Bar(1,b=2)
    print(res)#None
    
    ...

    此时可以加上返回值:

    import time
    def CalTime(func): def wrapper(*args,**kwargs):#相当于Bar(1,b=2) args=(1,) kwargs={"b":2} start_time=time.time() res=func(*args,**kwargs) #运行Bar() func(*(1,),**{'b':18}) 使用变量res存放被装饰函数的返回 end_time=time.time() print(end_time-start_time) return res #包装函数并且返回res,实现返回值的传递 return wrapper @CalTime # Bar=CalTime(Bar) def Bar(a,b): time.sleep(2) return "我是bar函数" res=Bar(1,b=2) print(res)#None #########################输出################ #2.0001144409179688 #我是bar函数

    3、functools.wraps

    如何保持原函数的元信息呢?

    从上面的代码如果打印:

    ...
    
    res=Bar(1,b=2)
    print(res)
    print(Bar.__name__) #wrapper
    
    ...

    可以看到打印Bar函数名字输出wrapper,这样并没有保留原函数的元信息,此时可以利用functools.wraps达到这个效果:

    import time
    import functools
    
    def CalTime(func):
        @functools.wraps(func)
        def wrapper(*args,**kwargs):#相当于Bar(1,b=2) args=(1,) kwargs={"b":2}
            start_time=time.time()
            res=func(*args,**kwargs) #运行Bar() func(*(1,),**{'b':18})  使用变量res存放被装饰函数的返回
            end_time=time.time()
            print(end_time-start_time)
            return res  #包装函数并且返回res,实现返回值的传递
        return wrapper
    
    @CalTime # Bar=CalTime(Bar)
    def Bar(a,b):
        time.sleep(2)
        return "我是bar函数"
    
    res=Bar(1,b=2)
    print(res)
    print(Bar.__name__) #Bar
    加入functools.wraps

     此时一个完整的装饰器就出来了。

    4、装饰器接收参数

      有时针对不同的被装饰的函数,装饰器需要做一些调整,可以将装饰器传入需要的参数,完成一定的功能。

     这是一般的装饰器:

    水果装饰器
    import functools
    
    def fruit(func):
        @functools.wraps(func)
        def wrapper(*args,**kwargs):
            print("颜色:%s,价格:%s"%(args[0],args[1]))
            return func(*args,**kwargs)
        return wrapper
    
    @fruit
    def apple(color,price):
    
        return "我爱吃苹果"
    
    @fruit
    def pear(color,price):
    
        return "我爱吃梨子"
    
    apple("绿色",20)#颜色:绿色,价格:20
    pear("黄色",10) #颜色:黄色,价格:10

    现在需求是根据不同的爱好选择不同的水果,也就是传入不同水果类型,执行不同水果函数:

    import functools
    
    def outer(type=None):
        def fruit(func):
            @functools.wraps(func)
            def wrapper(*args,**kwargs):
                print("颜色:%s,价格:%s"%(args[0],args[1]))
                if type=="apple":
                    return func(*args,**kwargs)
                elif type=="pear":
                    return func(*args, **kwargs)
                else:
                    print("无可用函数执行!")
            return wrapper
        return fruit
    
    @outer(type="apple") #@fruit 相当于apple=fruit(apple)
    def apple(color,price):
    
        return "我爱吃苹果"
    
    @outer(type="pear")
    def pear(color,price):
    
        return "我爱吃梨子"
    
    res=apple("绿色",20)#颜色:绿色,价格:20
    print(res)#我爱吃苹果
    # pear("黄色",10) #颜色:黄色,价格:10

    从上面可以看出,这样又加一层嵌套函数,但实际作用就是传入一个type值,其它的还是没变:

    @outer(type="apple")----------》@fruit------------》相当于apple=fruit(apple)

    这也是对装饰器的进一步扩展了。

    三、实例

    1、登陆验证

      一般web页面登陆后需要记录登录后用户的信息,这样进入其它页面就不用再次登陆了,这里模拟一下登陆认证,首先可以写一个登陆的处理函数,如果登陆成功后,将用户的的信息写入session中:

    def login(request):
        if request.method=="GET":
            return render(request,'login.html')
        username=request.POST.get("username")
        password=request.POST.get("password")
        user=models.UserInfo.objects.filter(username=username,password=password).first()
        if user:
            request.session['user_info'] = user
            return redirect("/index/")
    login

    写一个装饰器,进入主页后的登陆判断:

    def check_login(func):
        def wrapper(request,*args,**kwargs):
            if request.session['user_info']:
                return func(request,*args,**kwargs)
            else:
                return redirect("/login/")
        return wrapper

    再进入其它页面使用这个装饰器:

    @check_login
    def index(request):
    
        return render(request,"index.html")

    2、登陆验证类型的选择

      上面使用的session进行判断的,当然,还可以给装饰器加上参数,选择不同的验证方式,只需要做简单的修改:

    def auth_type(type=None):
        def check_login(func):
            def wrapper(request,*args,**kwargs):
                if type == "file": #从文件中取出用户名和密码进行校验
                    print("用文件的方式进行验证")
                    if request.POST.get("username") == "xxx" and request.POST.get("passworld") == "yyy":
                        return func(request, *args, **kwargs)
                    else:
                        return redirect("/login/")
                else:
                    if request.session['user_info']:
                        return func(request,*args,**kwargs)
                    else:
                        return redirect("/login/")
            return wrapper
        return check_login

    其它的函数被装饰:

    @auth_type(type="file")
    def home(request):
    
        return render(request,"home.html")
    
    @auth_type
    def index(request):
    
        return render(request,"index.html")
  • 相关阅读:
    Java泛型学习笔记
    Java泛型学习笔记
    Java泛型学习笔记
    Java泛型学习笔记
    Java泛型学习笔记
    Java泛型学习笔记
    Java泛型学习笔记
    有1到100共100个数, 从1开始, 每隔1, 2, 3... 个数拿走一个数, 最后剩下几?(约瑟夫环)
    推荐一个自动抽取pdf高亮笔记的web应用
    协程
  • 原文地址:https://www.cnblogs.com/shenjianping/p/11031239.html
Copyright © 2020-2023  润新知