PHP前端开发

深入了解Python装饰器函数

百变鹏仔 3个月前 (01-22) #Python
文章标签 函数

本篇文章给大家带来了关于python的相关知识,其中主要整理了装饰器函数的相关问题,包括了装饰器的形成过程、本质与功能、进阶与优化等等内容,下面一起来看一下,希望对大家有帮助。

推荐学习:python

假如我写了一个函数 f

def f():    print('hello')

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

立即学习“Python免费学习笔记(深入)”;

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

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

import timedef 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 timedef f():    print('hello')def timer(func):    def inner():        start = time.time()        func()        print(time.time() - start)    return innerf = timer(f)f()

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



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

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

import timedef 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、功能

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

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

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

import timedef timer(func):    def inner(a):        start = time.time()        func(a)        print(time.time() - start)    return inner@timerdef f(a):    print(a)f('hello')

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

import timedef 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 func10.0in func2 and get a:aaaaaa0.0fun2 over

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

import timedef 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:aaaaaa0.0in func2 and get a:aaaaaa0.0fun2 over

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

ef wrapper1(func):   #func ----- f    def inner1():        print('wrapper1 ,before func')        func()        print('wrapper1 ,after func')    return inner1def 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) = innerdef f():    print('in f')f()    #===>>inner2#多个装饰器装饰同一个函数输出结果:wrapper2 ,before funcwrapper1 ,before funcin fwrapper1 ,after funcwrapper2 ,after func


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

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

'''为了使装饰器不用时能够更好的回收而不是一个一个去注释或者删除我们引入带参数的装饰器概念'''import time'''FLAGE的目的是用它控制装饰器的开关,那么当我们不用的时候就不要一个一个去注释只需将True改为False就行'''FLAGE = Truedef 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()输出结果:wahahahahahaha0.10152268409729004erguotoutoutou0.10795140266418457
'''print(wahaha.__name__)      #查看字符串格式的函数名print(wahaha.__doc__)       #查看一个函数的注释'''#下面用__name__查看holiday的函数名from functools import wrapsdef 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.对原函数的功能扩展是开放的

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

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

2.对修改是封闭的

        为什么要对修改封闭呢?

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

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



小结:

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

推荐学习:python