七、装饰器

Python允许使用装饰器对函数进行装饰,装饰器可以帮助函数实现一些通过的功能,在函数调用前运行些预备代码或函数调用后执行清理工作。如:插入日志、检测性能(计时)、事务处理、缓存、权限校验等。这样编写函数时就可以专注于功能的实现,就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

定义:装饰器本质就是函数,功能是在不更改原函数的代码前提下给函数增加新的功能。包装后返回一个装饰后的函数对象,该函数对象将更新原函数对象,程序将不再能访问原始函数对象。

参考了其他博客:

https://blog.csdn.net/qq_26442553/article/details/82226657

https://blog.csdn.net/xiangxianghehe/article/details/77170585

https://blog.csdn.net/u010358168/article/details/77773199

一、装饰器引入

以下面的函数为例,要求计算运行时间(性能),如何处理?

import time
def func():
    print("hello")
    time.sleep(1)
    print("world")

1.可以在原代码基础上进行修改

import time
def func():
    starttime = time.time()

    print("hello")
    time.sleep(1)
    print("world")

    endtime = time.time()
    t = (endtime - starttime) * 1000
    print 'the time is %d ms' % t

func()

2.但这样显然不好,如果还有其他函数也有这个需求,不可能对每个函数都进行修改,可以如下方法解决

import time
def deco(f):
    starttime=time.time()
    f()
    endtime=time.time()
    t=(endtime-starttime)*1000
    print 'the time is %d ms'%t
    

def func():
    print("hello")
    time.sleep(1)
    print("world")

deco(func)

'''结果如下:
hello
world
the time is 1000 ms
'''

3.这时候存在的问题是:如果有其它的函数(比如func1()、func2()...)也需要计时,那么要实现这个功能,每个调用处都要改为deco(func1)、deco(func2)...。再另外:如果需要多个修饰的函数的话,那上面函数调用麻烦了,需要一层层嵌套,如deco1(deco2(func1))。

如何解决这个问题呢,终于到装饰器登场了!

import time
def deco(func):
    print 'mm'
    def wrapper():
        start_time=time.time()
        func()
        end_time=time.time()
        spend_time=(end_time-start_time)*1000
        print 'the time is%d'%spend_time
    return wrapper

@deco
def func():
    print 'hello'
    time.sleep(1)
    print 'cc'

func()

此处的deco函数就是最原始的装饰器,它的参数是一个函数,然后返回值也是一个函数。经过@deco装饰后,func()=deco(func()),扩展了func()函数的功能,代码简洁明了。

二、单个装饰器:装饰器执行原理

# -*- coding:utf-8 -*-
def deco1(func):
    print '---deco1---'
    def wrapped():
        print '---111111---'
        func()
        print '---222222---'
    return wrapped

@deco1
def func():
    print '------func------'

'''1、这时不调用函数,只是执行上面的代码,也会有结果,结果为
---deco1---'''

func()
'''2、调用函数,结果为
---deco1---
---111111---
------func------
---222222---'''

在1处也会有结果输出,因为经过@deco1装饰后,其效果等同于:deco(func()),即调用了deco()函数,其参数是func(),所以会打印‘---deco1---’。

在2处,执行func(),相当于执行deco(func()),顺序执行即可:首先打印‘---111111---’,而后调用func(),打印'------func------',最后打印'---222222---'

三、多个装饰器:执行顺序

# -*- coding:utf-8 -*-
def deco1(func):
    print '---deco1---'
    def wrapped1():
        print '---111111---'
        func()
        print '---222222---'
    return wrapped1

def deco2(func):
    print '---deco2---'
    def wrapped2():
        print '---aaaaaa---'
        func()
        print '---bbbbbb---'
    return wrapped2

@deco1
@deco2
def func():
    print '------func------'
'''1、这里不调用函数,执行上面代码,其结果为:
---deco2---
---deco1---
'''

func()
'''2、调用函数,结果为
---deco2---
---deco1---
---111111---
---aaaaaa---
------func------
---bbbbbb---
'''

在1处,从结果可以看到,多个装饰器装饰时,装饰器的加载顺序是从内到外的

首先是@deco2装饰func(),func()=deco2(func()),[ 返回值是wrapped2 ]。====>>打印'---deco2---'

之后再由@deco1进行装饰,此时func()=deco1(deco2(fucn()))=deco1(wrapped2),[ 返回值是wrapped1]。====>>打印'---deco1---'

此时执行deco1(),相当于执行wrapped1(),顺序执行wrapped1(),====>>首先打印'---111111---'。

而后执行wrapped1()里面的func(),此时的func()即为wrapped2,即执行wrapped2(),顺序执行wrapped2(),=>>首先打印'---aaaaaa---',   ====>>之后打印'------func------'。这是wraped2()执行完毕。

执行wrapped1()的最后一条语句,====>>打印'---bbbbbb---'。

由上可知:

1.当一个函数被多个装饰器装饰时,装饰器的加载顺序是从内到外的(从下往上的)。其实很好理解:装饰器是给函数装饰的,所以要从靠近函数的装饰器开始从内往外加载。

2.外层的装饰器,是给里层装饰器装饰后的结果进行装饰。相当于外层的装饰器装饰的函数是里层装饰器的装饰原函数后的结果函数(装饰后的返回值函数)。(引用自https://blog.csdn.net/qq_26442553/article/details/82226657

四、被装饰的函数不带参数 VS 被装饰的函数带参数

之前的例子中,被装饰的函数都是不带参数的,如果被装饰的函数带参数的话就会出错了,此处注意,被装饰的函数带参数,不同于带参数装饰器。

# -*- coding:utf-8 -*-
import time
#定义一个计时函数,其参数为一个函数,用于接收被装饰的函数
def time_stt(func):
    #定义一个内嵌的包装函数,记录函数开始时间和结束时间
    def wrapper(*t,**d):
        start=time.time()
        func(*t,**d)
        usetime=time.time()-start
        print u'执行函数,',func.__name__,u'用时',usetime,'秒'
    return wrapper

print u'装饰无参数函数实验'
@time_stt
def test():
    time.sleep(3)

test()
print

print u'装饰一个参数函数实验'
@time_stt
def pr(n):
    for i in range(n):
        print i,
    print

pr(45)
print

print u'装饰两个参数函数实验'
@time_stt
def area(l,w):
    print u'面积为:',l*w

area(355,564)


'''结果为
装饰无参数函数实验
执行函数, test 用时 3.0 秒

装饰一个参数函数实验
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
执行函数, pr 用时 0.0 秒

装饰两个参数函数实验
面积为: 200220
执行函数, area 用时 0.0 秒
'''

五、无参数装饰器

装饰器是一个函数,那么装饰器也可以有参数,所以装饰器可以分为无参数装饰器和带参数装饰器。之前的例子都是无参数装饰器。

六、带参数装饰器

格式:@装饰器函数(参数)

上个代码就明白了:

# -*- coding:utf-8 -*-
import time
#根据装饰器的时间选择是记录函数开始调用的时间还是函数结束的时间
def decselect(sel):
    def startdec(func):
        def r(*t,**d):
            print u'下面调用函数:',func.__name__,u'开始调用时间为:',time.ctime()
            func(*t,**d)
        return r
    def enddec(func):
        def r(*t,**d):
            func(*t,**d)
            print u'函数:', func.__name__, u'于', time.ctime(),'结束调用'
        return r
    try:
        return {'start':startdec,'end':enddec}[sel]
    except KeyError,e:
        raise ValueError(e),u'必须是“start"或是"end"'

#采用@decselect('end')对sp()进行装饰
@decselect('end')
def sp(seq):
    #输出一个序列
    for n in seq:
        print n,
    print

sp([1,2,3,4,4])
print

#采用@decselect('start')对sp()进行装饰
@decselect('start')
def sp(seq):
    '输出一个序列'
    for n in seq:
        print n,
    print

sp([1,2,3,4,4])

'''结果为
1 2 3 4 4
函数: sp 于 Sat Sep 08 00:26:42 2018 结束调用

下面调用函数: sp 开始调用时间为: Sat Sep 08 00:26:42 2018
1 2 3 4 4
'''

七、python装饰器的wraps作用

Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wraps,它能保留原有函数的名称和docstring。

不加wraps

def deco1(func):
    print '---deco1---'
    def wrapped():
        print '---111111---'
        func()
        print '---222222---'
    return wrapped

@deco1
def func():
    'docstring'
    print '------func------'

print func.__name__,func.__doc__

'''结果
wrapped None
'''

加wraps

from functools import wraps  
def deco1(func):
    print '---deco1---'
    @wraps(func)
    def wrapped():
        print '---111111---'
        func()
        print '---222222---'
    return wrapped

@deco1
def func():
    'docstring'
    print '------func------'

print func.__name__,func.__doc__

'''结果
func docstring
'''

八、装饰器的应用举例

参考博文:https://blog.csdn.net/qq_26886929/article/details/54091962

                  https://blog.csdn.net/qq_26886929/article/details/54171650

猜你喜欢

转载自blog.csdn.net/cc27721/article/details/82490553