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#相当于:func=deco(func)
def func():
print 'hello'
time.sleep(1)
print 'cc'
func()
此处的deco函数就是最原始的装饰器,它的参数是一个函数,然后返回值也是一个函数。经过@deco装饰后,func()=deco(func)(),扩展了func()函数的功能,代码简洁明了。
装饰器两个原则:
1、不修改被修饰函数的原代码
2、不修改被修饰函数的调用方式。
装饰器的基础知识:=高阶函数+函数嵌套+闭包
然后给装饰器里的闭包加上返回值:
import time
def deco(func):
print 'mm'
def wrapper():
start_time=time.time()
res = func()
end_time=time.time()
spend_time=(end_time-start_time)*1000
print 'the time is%d'%spend_time
return res
return wrapper
@deco#相当于:func=deco(func)
def func():
print 'hello'
time.sleep(1)
print 'cc'
return '这是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): #定义函数时,*args,**kwargs可以接收任意参数
start=time.time()
func(*t,**d) #调用函数时,*args,**kwargs解包接收的参数
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
'''
另外一个例子(参考https://www.cnblogs.com/linhaifeng/articles/7532497.html#_label5,看的视频):
user_list = [
{'name':'cc', 'pwd':'123'},
{'name':'dd', 'pwd':'123'},
{'name':'mm', 'pwd':'234'},
]
current_user = {'name':None,'login':False}
def auth_type(type='cc'):#这个作用是又加了一层,实现带参数的装饰器
def func(func):
def wrapper(*args, **kwargs):
print('type',type)
if current_user['name'] and current_user['login']:
res = func(*args, **kwargs)
return res
name = input('please enter name:').strip()
pwd = input('please enter pwd:').strip()
for i in user_list:
if name == i['name'] and pwd == i['pwd']:
current_user['name'] = name
current_user['login'] = True
res = func(*args, **kwargs)
return res
else:
print('wrong')
return wrapper
return func
@auth_type(type='cc') #@auth_type(type='cc')即--->@func,同时传了一个参数type='cc'
def index():
print('index')
@auth_type(type='dd')
def shoping(name):
print('{}里的东西有哈哈'.format(name))
index()
shoping('cc')
七、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