【Python基础篇007】带你超深度剖析Python装饰器函数

 

目录

🎑前言:

🎑一、装饰器 —— 形成过程

🎑二、装饰器 —— 初识语法糖

🎑三、装饰器 ——本质与功能

🎑四、装饰器 —— 装饰带参数,返回值的装饰器

💥1、装饰带一个参数的函数

💥2、装饰多个带有不同参数但无返回值的函数

💥3、装饰多个带有不同参数且有返回值的函数

💥4、多个装饰器装饰同一个函数

🎑五、装饰器 ——  装饰器进阶与优化

💥1、带参数的装饰器

💥2、防止函数必要信息失效

🎑六、装饰器 —— 装饰原则

💥1、开放封闭原则

🎑小结:

💥1、装饰器的固定格式(模板)



🎑前言:

假如我写了一个函数 f

def f():
    print('hello')

之后我想知道这段函数执行所要的时间,这好办,我只要将代码改为如下就行

import time
def f():
    start = time.time()   #获取程序执行开始的时间
    print('hello')
    end = time.time()     #获取程序执行结束的时间
    print(end - start)    #得出函数f执行所要时间

f()

但之后我有写了无数个函数f2,f3……fn,我想知道每个函数执行所需要的时间,那么如果都像上面一样改,岂不是很闹心?还是不行,因为这样实在是太麻烦了。那怎么办呢?于是灵机一动,写了一个timer函数。。。

import time
def timer(func):
    start = time.time()
    func()
    print(time.time() - start)

def f():
    print('hello')


def f2():
    print('xorld')

timer(f)
timer(f2)

这样看起来是不是简单多啦?不管我们写了多少个函数都可以调用这个计时函数来计算函数的执行时间

但是如果我只想用原来的方式f1(),f2(),fn()调用了这个函数,函数在原本执行输出的结果不变的前提下还可以增加计算时间的功能,而不是调用timer(f),timer(f2)才能计算时间,这该怎么办呢?

看了下面的装饰器函数你就会知道如何解决这个问题



🎑一、装饰器 —— 形成过程

以下就是解决上面问题的代码的简单版:

import time

def f():
    print('hello')

def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

f = timer(f)
f()

还是这句话我只想用原来的方式f1(),f2(),fn()调用了这个函数,函数在原本执行输出的结果不变的前提下还可以增加计算时间的功能,但我还是要在函数 f 执行前写 f = timer(f)在这一串代码,是不是觉得碍眼?python的开发者也觉得碍眼,所以python的开发者就为我们提供了一句语法糖来解决这个问题!



🎑二、装饰器 —— 初识语法糖

用@timmer代替f = timer(f),这就是一句语法糖。

import time
def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner

@timer   #==> 写着这句话就相当于执行了f = timer(f)
def f():
    print('hello')


f()



🎑三、装饰器 ——本质与功能

1、本质

        装饰器的本质就是一个闭包函数

2、功能

        在不修改原函数及其调用方式的情况下对原函数功能进行扩展



🎑四、装饰器 —— 装饰带参数,返回值的装饰器

  • 💥1、装饰带一个参数的函数

  • 刚才我们写的装饰器都是装饰不带参数的函数,现在要装饰一个带参数的函数怎么办呢?

    import time
    def timer(func):
        def inner(a):
            start = time.time()
            func(a)
            print(time.time() - start)
        return inner
    
    @timer
    def f(a):
        print(a)
    
    f('hello')
  • 💥2、装饰多个带有不同参数但无返回值的函数

  • 其实装饰带参的函数并不是什么难事,但假如你有两个函数,需要传递的参数不一样呢,比如  函数func1有两个参数func1(a ,b),函数func 2只有一个参数func2(a), 且它们都想用这个装饰器装饰,做到计算函数执行时间?这怎么办呢?那就用下面代码。

    import time
    def timer(func):
        def inner(*args,**kwargs):
            start = time.time()
            re = func(*args,**kwargs)
            print(time.time() - start)
            return re
        return inner
    
    @timer   #==> func1 = timer(func1)
    def func1(a,b):
        print('in func1')
    
    @timer   #==> func2 = timer(func2)
    def func2(a):
        print('in func2 and get a:%s'%(a))
        return 'fun2 over'
    
    func1('aaaaaa','bbbbbb')
    print(func2('aaaaaa'))
    
    输出结果:
    in func1
    0.0
    in func2 and get a:aaaaaa
    0.0
    fun2 over
  • 💥3、装饰多个带有不同参数且有返回值的函数

  • 现在参数的问题已经完美的解决了,可是如果你的函数是有返回值的呢?用上面的代码你就拿不到返回值了那究竟要如何解决这个问题呢?那就看下面的代码吧!

    import time
    def timer(func):
        def inner(*args,**kwargs):
            start = time.time()
            re = func(*args,**kwargs)
            print(time.time() - start)
            return re
        return inner
    
    @timer   #==> func2 = timer(func2)
    def func2(a):
        print('in func2 and get a:%s'%(a))
        return 'fun2 over'
    
    func2('aaaaaa')
    print(func2('aaaaaa'))
    
    输出结果:
    in func2 and get a:aaaaaa
    0.0
    in func2 and get a:aaaaaa
    0.0
    fun2 over
  • 💥4、多个装饰器装饰同一个函数

  • 有些时候,我们也会用到多个装饰器装饰同一个函数的情况。

    ef wrapper1(func):   #func ----- f
        def inner1():
            print('wrapper1 ,before func')
            func()
            print('wrapper1 ,after func')
        return inner1
    
    def wrapper2(func):
        def inner2():
            print('wrapper2 ,before func')
            func()
            print('wrapper2 ,after func')
        return inner2
    
    @wrapper2       #f = wrapper2(f) ----->> wrapper2(inner1)  == inner2
    @wrapper1       #f = wrapper1(f) = inner
    def f():
        print('in f')
    f()    #===>>inner2
    #多个装饰器装饰同一个函数
    
    输出结果:
    wrapper2 ,before func
    wrapper1 ,before func
    in f
    wrapper1 ,after func
    wrapper2 ,after func



    🎑五、装饰器 ——  装饰器进阶与优化

  • 💥1、带参数的装饰器

  • 上面那个装饰器已经非常beautiful了,但是还有一个问题,如果我给代码中无数个函数都加了@timer这个语法糖,如果之后我又不想用它了那岂不是又要每个去将它注释,没日没夜忙活3天?岂不是特别麻烦,为了使装饰器不用时能够更好的回收而不是一个一个去注释或者删除 我们引入带参数的装饰器概念

    '''
    为了使装饰器不用时能够更好的回收而不是一个一个去注释或者删除
    我们引入带参数的装饰器概念
    '''
    
    import time
    '''FLAGE的目的是用它控制装饰器的开关,
    那么当我们不用的时候就不要一个一个去注释只需将True改为False就行'''
    
    FLAGE = True
    def timmer_out(flag):
        def timmer(func):
            def inner(*args,**kwargs):
                if flag:
                    start = time.time()
                    ret = func(*args,**kwargs)
                    end = time.time()
                    print(end - start)
                    return ret
                else:
                    ret = func(*args, **kwargs)
                    return ret
            return inner
        return timmer
    @timmer_out(FLAGE)
    
    #timmer_out(FLAGE)
    # 也相当于执行  timmer_out(FLAGE)--->>返回timmer———————>>@timmer(wahaha = timmer(wahaha))
    def wahaha():
        time.sleep(0.1)         #不休息的话函数执行的太快难以计算时间
        print('wahahahahahaha')
    
    wahaha()
    
    @timmer_out(FLAGE)
    def erguotou():
        time.sleep(0.1)         #不休息的话函数执行的太快难以计算时间
        print('erguotoutoutou')
    
    erguotou()
    
    输出结果:
    wahahahahahaha
    0.10152268409729004
    erguotoutoutou
    0.10795140266418457
    
  • 💥2、防止函数必要信息失效

  • '''
    print(wahaha.__name__)      #查看字符串格式的函数名
    print(wahaha.__doc__)       #查看一个函数的注释
    '''
    #下面用__name__查看holiday的函数名
    
    from functools import wraps
    def wrapper(func):
        @wraps(func)            #加在最内层函数正上方
        def inner(*args,**kwargs):
            print('在被装饰的函数执行之前做的事')
            ret = func(*args,**kwargs)
            print('在被装饰的函数执行之后做的事')
            return ret
        return inner
    
    @wrapper        #holiday = wrapper(holiday)
    def holiday(day):
        '''
        这是一个放假通知
        :param day:
        :return:
        '''
        print('全体放假%s天'%day)
        return '好开心'
    
    print(holiday.__name__)
    print(holiday.__doc__)
    '''
    结果是inner和None 但我们想要的是打印holiday的字符串格式的函数名和函数的注释这时该怎么办?
    解决方法就是  from functools import wraps
    使用语法是@wraps(被装饰的函数名)
    '''
    
    输出结果:
    holiday
    
        这是一个放假通知
        :param day:
        :return:
    



    🎑六、装饰器 —— 装饰原则

  • 💥1、开放封闭原则

  • 1.对原函数的功能扩展是开放的

            为什么要对功能扩展开放呢?

        对于任何一个程序来说,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许后来扩展、添加新功能。

    2.对修改是封闭的

            为什么要对修改封闭呢?

                    就像我们刚刚提到的,因为我们写的一个函数,很有可能在其他地方已经被导入使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经正在使用该函数的代码。

    装饰器就完美遵循了这个开放封闭原则。这就是学装饰器的初衷



    🎑小结:

  • 💥1、装饰器的固定格式(模板)

  • #格式一
    
    def timer(func):
        def inner(*args,**kwargs):
            '''执行函数之前要做的'''
            re = func(*args,**kwargs)
            '''执行函数之后要做的'''
            return re
        return inner
    
    #格式二
    
    from functools import wraps
    
    def deco(func):
        @wraps(func) #加在最内层函数正上方
        def wrapper(*args,**kwargs):
            return func(*args,**kwargs)
        return wrapper

    最后💖💖💖感谢各位能够看到这里💖💖💖:

    在鲁迅一篇未发表的文章中说过:“代码看懂了不是懂✨一定要自己实际操作哇✨这样才能更好的理解和吸收。”
    最后来一句:一个人可以在任何他怀有无限热忱的事情上成功,让我们一起进步吧✨✨

    来源:在下周周ovo

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Python基础篇007】带你超深度剖析Python装饰器函数

    发表评论