一、概念
1.装饰器是一个可调用对象,其参数是另一个函数(被装饰的函数)。一般装饰器的功能都是给一个已有的函数增加额外的功能。
二、Python内置的装饰器
Python中有一些内置的装饰器。其中functools模块中有wraps、lru_cache和singledispatch三个函数装饰器。此外还有property、classmethod以及staticmethod三个用于装饰方法的装饰器。
1.wraps装饰器
wraps装饰器能够把被装饰的函数的一些属性正确的留在装饰后的函数中。
示例:clock是一个用于计算函数运行时间的简单装饰器。
import time
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ','.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r'%(elapsed,name,arg_str,result))
return result
return clocked
注:r%是一个万能的格式符,会把参数原样打印,带有类型信息。
使用clock装饰器:
import time
from example7_15 import clock
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
if __name__ == '__main__':
snooze(0.123)
print(snooze.__name__,snooze.__doc__)
print('*********'*5)
print(factorial.__name__,factorial.__doc__)
factorial(30)
[0.13834610s] snooze(0.123) -> None
clocked None
*********************************************
clocked None
[0.00000040s] factorial(1) -> 1
...
clock装饰器能够达到预期的效果,正确的显示函数的执行时间。但是,却修改掉了被装饰函数的一些属性,并且不能支持关键字传参。这不是我们希望看到的。
改进:clock使用wrap装饰器
import time
import functools
def clock(func):
@functools.wraps(func)
def clocked(*args,**kwargs):
t0 = time.time()
result = func(*args,**kwargs)
elapsed = time.time() - t0
name = func.__name__
arg_lst = []
if args:
arg_lst.append(', '.join(repr(arg) for arg in args))
if kwargs:
pairs = ['%s = %r' %(k,w) for k,w in sorted(kwargs.items())]
arg_lst.append(', '.join(pairs))
arg_str = ', '.join(arg_lst)
print('[%0.8fs] %s(%s) -> %r'%(elapsed,name,arg_str,result))
return result
return clocked
改进之后的装饰器弥补了上述的缺点。
2.lru_cache装饰器
lru_cache装饰器实现了备忘功能。这是一项优化技术,他把函数的结果储存起来,避免传入相同参数是进行重复的计算。lru是lease recently used的缩写。在递归函数中使用这个装饰器速度提升效果非常明显。
示例:生成n个斐波那契数
from example7_17 import clock
@clock
def f(n):
return f(n-2) + f(n-1) if n >2 else 1
if __name__ == '__main__':
print(f(20))
在这里插入代码片[0.00000000s] f(2) -> 1
[0.00000000s] f(1) -> 1
[0.00000000s] f(2) -> 1
[0.00000000s] f(3) -> 2
[0.00000000s] f(4) -> 3
[0.00000000s] f(1) -> 1
[0.00000000s] f(2) -> 1
...
[0.19500208s] f(19) -> 4181
[0.25860739s] f(20) -> 6765
6765
使用前面的clock装饰器进行计时,可以看到普通的递归可以正确输出结果,但是有很多次的f都重复计算了,比如f(1),f(2)…
使用lru_cache装饰后,会把之前计算的f(1),f(2)…结果给记住。
import functools
from example7_17 import clock
@functools.lru_cache()
@clock
def f(n):
return f(n-2)+f(n-1) if n>2 else 1
if __name__ == '__main__':
print(f(20))
[0.00000000s] f(2) -> 1
[0.00000000s] f(1) -> 1
[0.00000000s] f(3) -> 2
[0.00000000s] f(4) -> 3
[0.00000000s] f(5) -> 5
[0.00000000s] f(6) -> 8
[0.00000000s] f(7) -> 13
[0.00000000s] f(8) -> 21
[0.00000000s] f(9) -> 34
[0.00000000s] f(10) -> 55
[0.00000000s] f(11) -> 89
[0.00000000s] f(12) -> 144
[0.00000000s] f(13) -> 233
[0.00000000s] f(14) -> 377
[0.00000000s] f(15) -> 610
[0.00000000s] f(16) -> 987
[0.00000000s] f(17) -> 1597
[0.00000000s] f(18) -> 2584
[0.00000000s] f(19) -> 4181
[0.00000000s] f(20) -> 6765
6765
每个f只会计算一次,效率明显提升了。
3.singledispatch装饰器
singledispatch装饰器可以实现创建一个泛函数,这个泛函数把多个函数绑定在一起。
需求:实现一个生成HTML的函数,这个函数根据传入的对象不同给对象加上不同的标签。
示例:
from functools import singledispatch
from collections import abc
import numbers
import html
@singledispatch
def htmlize(obj):
content = html.escape(repr(obj))
return '<pre>{}</pre>'.format(content)
@htmlize.register(str)
def _(text):
content = html.escape(text).replace('\n','<br>\n')
return '<p>{}</p>'.format(content)
@htmlize.register(numbers.Integral)
def _(n):
return '<pre>{0} (0x{0:x})</pre>'.format(n)
@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
return '<ul>\n<li>' + inner + '</li>\n</ul>'
print(htmlize({1, 2, 3}))
print(htmlize(abs))
print(htmlize('heimlich & Co.\n- a game'))
print(htmlize(42))
print(htmlize(['alpha', 66, {3, 2, 1}]))
可以看到singledispatch装饰器生成了一个htmlize.register装饰器,这个装饰器将专门的函数注册为泛函数。
注:
(1)_是Python中用于无意义的临时变量的写法,与普通的变量没有区别;
(2)专门函数尽量用来处理一个抽象基类(numbers.Integral、abc.MutableSequence),而不是具体的类(int、list)。
(3)叠放的装饰器从最底下开始执行。例如有
@decorator1
@decorator2
def f():
pass
那么相当于执行了f = decorator1(decorator2(f)),类似于数学中的复合函数。
459

被折叠的 条评论
为什么被折叠?



