Python3中装饰器
什么是装饰器?
装饰器,顾名思义,狭义的可以理解为起到一个装饰的作用。
即定义一个函数后,想要重新使这个函数增加新的功能,但是不改变原函数的内容。可以想象着进行一个包装,然后再调用原函数,其实就是调用的包装后的函数。
下面我们通过实例进行阐述:
无参数装饰器
import time #def caltime(f):就是一个装饰器,
#装饰器一般都写在顶上,当函数要调用装饰器的时候
#用@进行连接 下文中的@caltime
def caltime(f):
def func():
t1 = time.time() #上文导入时间包,t1开始 t2结束 计算f()运算时间
f()
t2 = time.time()
print(t1)
print(t2)
print(t2 - t1)
return func #返回func函数
@caltime #连接上文的装饰器
def getwater():
print('取了一杯水')
print(pow(2,100)/11*30+23**23)
getwater()
#(输出) 取了一杯水
2.4337696909561263e+31
1539880939.1847699
1539880939.1857696
0.0009996891021728516
@caltime这个语句相当于 执行 getwater = caltime(getwater),为getwater函数装饰并返回func
即getwater=func (实则getwater指向func函数)
所以 getwater()= func()
调用getwater() 实际是调用func()函数。
路径为下:
getwater()-->进入@caltime-->执行def caltime(f)--
#(@caltime-->执行def func中间并没有运行func函数,因为并没有进行调用函数func)
--->return func-->func()-->print(t2 - t1)
有参数的装饰器
import time
def caltime(flag):
def myfunc(somefunc):
def func(*args, **kwargs):
t1 = time.time()
result = somefunc(*args, **kwargs)
t2 = time.time()
if flag:
print(t2 - t1)
else:
print('笑脸', t2 - t1)
return result
return func
return myfunc
@caltime(flag = True)
def sayhello():
print('hello world')
if __name__ == '__main__':
sayhello()
# 分解:
# 1、calltime(flag = False)
# 必须返回一个函数,并且带有一个参数,该参数是函数 =>myfunc(f)
# 2、sayhello传入到上述返回的函数参数进行传递: 并且返回一个函数 且返回的这个函数是被装饰过的
# sayhello = myfunc(sayhello)
# 3、调用sayhello,实际调用被装饰过的这个函数
# 4、然后继续往下,sayhello = myfunc(sayhello)=func(myfunc(sayhello))
#实际上调用sayhello(),就为调用func()
#步骤:caltime(flag = True)-->def caltime(flag)-->return myfunc-->@caltime(flag = True)
# -->def func(*args, **kwargs)-->return func-->@caltime(flag = True)
# -->if __name__ == '__main__': --> sayhello() ---> t1 = time.time() 到return result
#实际上来说:第一个return 为 myfunc, 接下来执行myfunc(sayhello),相当于sayhello = myfunc,
# 而参数 somefunc = sayhello 然后
# 第二个return 为func,并使用原函数名进行接收,相当于:sayhello = myfunc = func,
# 最后当调用sayhello()时,执行上述的func(),被装饰过的函数
3.@wraps(fn)装饰,解决不能调出函数的help说明
# 2 装饰器里的@wraps用法
# 我们看这样一个例子:
import time
def timeit(fn):
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
print(time.time() - start)
return ret
return wrapper
@timeit
def add(x, y):
'''x + y'''
return x + y
help(add)
#(输出)Help on function wrapper in module __main__:
# wrapper(*args, **kwargs)
# 我们本意是求add的用法,和add的名字,结果给的不是我们想要的,是因为我们调用add的帮助和名
# 字,实际上调用的是wrapper,所以我们返回的都是wrapper的help和name。
# 这里就可以在装饰器timeit里面用@wraps(fn)方法,这个方法在标准库functools里面。这样我们就可以
# 得到想要的结果了,查看帮助是add的帮助,name是add的name:
import time
from functools import wraps
def timeit(fn):
@wraps(fn) # 下面的函数wrapper就集成了传入的函数的属性。
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
print(time.time() - start)
return ret
return wrapper
@timeit
def add(x, y):
'''x + y'''
return x + y
help(add)
#(输出)Help on function add in module __main__:
#
# add(x, y)
# x + y
4.总结详解
# 1 不带参数的装饰器
# 先看一个没有用到装饰器的代码:
import time
def sleep(n):
time.sleep(n)
start = time.time()
sleep(3)
print(time.time()-start)
#(输出)3.0056068897247314
# 上述代码的作用就是算出sleep(3)这个的执行时间。
# 我们可以改写代码,使得计算函数执行时间的代码作为一个函数,接收一个函数,然后计算这个函数的
# 执行时间。
import time
def timeit(fn, *args, **kwargs):
start = time.time()
ret = fn(*args,**kwargs)
print(time.time()-start)
return ret
def sleep(n):
time.sleep(n)
timeit(sleep,3)
# 上面的代码,必须用timeit来传入sleep函数和sleep的参数来运行,我们可不可以提前装饰下要被装饰的
# 函数,然后调用呢?可以的,如下:
import time
def timeit(fn):
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
print(time.time()-start)
return ret
return wrapper
def sleep(n):
time.sleep(n)
sleep = timeit(sleep) #提前把sleep函数装饰了。
sleep(3) # 再调用函数的时候,就是调用被装饰过的sleep函数了。
#(输出)3.000748872756958
# Python提供了一种装饰器的语法糖,可以把这三行:
def sleep(n):
time.sleep(n)
sleep = timeit(sleep)
# 改为下面这三行:
@timeit
def sleep(n):
time.sleep(n)
# 完整版语法糖版的装饰器示例如下:
import time
def timeit(fn):
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args,**kwargs)
print(time.time()-start)
return ret
return wrapper
@timeit
def sleep(n):
time.sleep(n)
sleep(3)
#(输出)3.004680871963501
# 装饰器的本质是高阶函数,接受一个函数作为参数,并且返回一个函数。
# 装饰器通常会返回一个封装函数(上述代码中的wrapper),这个封装函数在传入的函数前后做一些事
# 情。
# 详解上述代码:
# 我们在timeit装饰器里定义了一个封装函数wrapper,这个wrapper函数在我们真正传入的函数fn的前后
# 做了一些操作,计算出了fn运行的时间差,并且把函数返回回去,然后把这个封装函数wrapper返回回
# 去,替换原来的fn。
# @timeit 然后定义的函数sleep,意味着sleep = timeit(sleep),即sleep即为传入的函数,然后经过
# wrapper的封装,返回回来,替换原来的sleep。
# 2 装饰器里的@wraps用法
# 我们看这样一个例子:
import time
def timeit(fn):
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
print(time.time() - start)
return ret
return wrapper
@timeit
def add(x, y):
'''x + y'''
return x + y
help(add)
#(输出)Help on function wrapper in module __main__:
# wrapper(*args, **kwargs)
# 我们本意是求add的用法,和add的名字,结果给的不是我们想要的,是因为我们调用add的帮助和名
# 字,实际上调用的是wrapper,所以我们返回的都是wrapper的help和name。
# 这里就可以在装饰器timeit里面用@wraps(fn)方法,这个方法在标准库functools里面。这样我们就可以
# 得到想要的结果了,查看帮助是add的帮助,name是add的name:
import time
from functools import wraps
def timeit(fn):
@wraps(fn) # 下面的函数wrapper就集成了传入的函数的属性。
def wrapper(*args, **kwargs):
start = time.time()
ret = fn(*args, **kwargs)
print(time.time() - start)
return ret
return wrapper
@timeit
def add(x, y):
'''x + y'''
return x + y
help(add)
#(输出)Help on function add in module __main__:
#
# add(x, y)
# x + y
# 3 带参数的装饰器
# 一个程序占用的时间分为用户时间(包括sleep等待的时间),和cpu时间(cpu时间不包括sleep等待的
# 时间)。
# 下面是cpu时间的例子:
import time
def sleep(n):
time.sleep(3)
start = time.clock()
sleep(3)
print(time.clock() - start)
#(输出)2.9992514567665083
# 我们想写一个装饰器,既可以返回用户时间,又可以返回cpu时间,默认返回的是用户时间(可以用一
# 个开关作为参数,来控制返回的是什么时间),这时候我们就用到了带参数的装饰器:
# 上面是返回用户时间的装饰器。
# 如果后面几行像下面这样写,就是输出cpu时间了:
@timeit(cpu_time = True) # 传入cpu_time的参数为True,覆盖掉False默认参数
def fun(n):
time.sleep(n)
return n
print(fun(3))
# 带参数的装饰器本质是一个函数,传入参数,返回一个新的装饰器。
# 带参数的装饰器只允许一层,如果像这样@xxx()()多层了,就会报错。
import time
def timeit(cpu_time = False):
# 根据传入的参数,来决定是用time.clock还是time.time
time_func = time.clock if cpu_time else time.time
def dec(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
start = time_func()
ret = fn(*args, **kwargs)
print(time_func() - start)
return ret
return wrapper
return dec
# 下面注释,等同于@timeit()那段
# def fun(n):
# time.sleep(n)
# return n
# fun = timeit()(fun)
# 其实上面这行,用柯里化就非常好理解了。
@timeit() # 参数为空,但也是带了参数了,空参数,传入的参数为默认参数,等同于@timeit(cpu_time
def fun(n):
time.sleep(n)
return n
fun(3)
# 多个装饰器装饰的时候,采用就近原则,然后一层层装饰,比如:
#@a
#@b
#@c
#def fn():
pass
# 上述等效于a(b(c(fn)))
# 4 装饰器的应用
# 给函数调用做缓存
from functools import wraps
def memo(fn):
cache = {}
miss = object()
@wraps(fn)
def wrapper(*args):
result = cache.get(args, miss)
if result is miss:
result = fn(*args)
cache[args] = result
return result
return wrapper
@memo
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
# 上面这个例子中,是一个斐波拉契数例的递归算法。我们知道,这个递归是相当没有效率的,因为会重
# 复调用。比如:我们要计算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分
# 解成fib(2)+fib(1)…… 你可看到,基本上来说,fib(3), fib(2), fib(1)在整个递归过程中被调用了两次。
# 而我们用decorator,在调用函数前查询一下缓存,如果没有才调用了,有了就从缓存中返回值。一下
# 子,这个递归从二叉树式的递归成了线性的递归。
# 4.2 给函数打日志
# 下面这个示例演示了一个logger的decorator,这个decorator输出了函数名,参数,返回值,和运行时
# 间。
from functools import wraps
def logger(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
ts = time.time()
result = fn(*args, **kwargs)
te = time.time()
print("function = {0}".format(fn.__name__))
print(" arguments = {0} {1}".format(args, kwargs))
print(" return = {0}".format(result))
print(" time = %.6f sec" % (te-ts))
return result
return wrapper
@logger
def multipy(x, y):
return x * y
@logger
def sum_num(n):
s = 0
for i in range(n+1):
s += i
return s
print(multipy(2, 10))
print(sum_num(100))
print(sum_num(10000000))