函数装饰器功能
函数装饰器类似于设计模式中装饰器模式的思路,通过某种方式来增强函数的行为,其样子类似下面方式:
@decorate
def target():
print('running target()')
# 等价于
def target():
print('running target()')
target = decorate(target)上下两个代码的效果是一样的。在程序加载后,target 函数就不是无装饰器修饰的 target 函数了。当然,如果装饰器返回原对象,那么还是一样的函数,但一般不会这么做。
通过下面的结果可以看出 target 函数的逻辑被装饰器替换了。
>>> def deco(func):
... def inner():
... print('running inner()')
... return inner
...
>>> @deco
... def target():
... print('running target()')
...
>>> target()
running inner()一个简单的装饰器,用于输出函数的运行时间:
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
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
if __name__=='__main__':
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
print('6! =', factorial(6))
$ python3 clockdeco_demo.py
**************************************** Calling snooze(.123)
[0.12405610s] snooze(.123)-> None
**************************************** Calling factorial(6)
[0.00000191s] factorial(1)-> 1
[0.00004911s] factorial(2)-> 2
[0.00008488s] factorial(3)-> 6
[0.00013208s] factorial(4)-> 24
[0.00019193s] factorial(5)-> 120
[0.00026107s] factorial(6)-> 720
6! = 720带自定义参数的装饰器
def repeat(num):
def my_decorator(func):
def wrapper(*args, **kwargs):
for i in range(num):
func(*args, **kwargs)
return wrapper
return my_decorator
@repeat(4)
def greet(message):
print(message)
greet('hello world')类装饰器
类装饰器主要依赖于函数 __call__(),每当你调用一个类的示例时,函数 __call__() 就会被执行一次。
class Count:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print('num of calls is: {}'.format(self.num_calls))
return self.func(*args, **kwargs)
@Count
def example():
print("hello world")
example()
# 输出
num of calls is: 1
hello world
example()
# 输出
num of calls is: 2
hello world装饰器的顺序
@decorator1
@decorator2
@decorator3
def func():
...
decorator1(decorator2(decorator3(func)))标准库内置的装饰器
functools.wraps 构建行为良好的装饰器
上面方式实现的 clock 装饰器存在几个缺点:
- 不支持关键字参数。
- 经过装饰器装饰后,返回的是内部 clocked 函数,会遮盖了被装饰函数的
__name__和__doc__属性。即,在程序运行后,将 snooze 函数、factorial 函数分别赋值给两个对象时,检查这两个对象的__name__和__doc__属性会发现值相同,都是 clocked 函数的属性值。
通过 functools.wraps 装饰器可以将相关的属性从 func 复制到 clocked 中,还可以正确处理关键字参数。具体如下:
# clockdeco2.py
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 clockedfunctools.lru_cache 数据缓存
functools.singleddispatch 泛函数
叠放装饰器
装饰器的执行时间
装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。一般来说,就是在导入当前模块时执行装饰器函数。
registry = []
def register(func):
print('running register(%s)'%func)
registry.append(func)
return func
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
def main():
print('running main()')
print('registry->', registry)
f1()
f2()
f3()
if __name__=='__main__':
main()把 registration.py 当作脚本运行得到的输出如下:
$ python3 registration.py
running register(<function f1 at 0x100631bf8>)
running register(<function f2 at 0x100631c80>)
running main()
registry-> [<function f1 at 0x100631bf8>, <function f2 at 0x100631c80>]
running f1()
running f2()
running f3()如果导入 registration.py 模块(不作为脚本运行),输出如下:
>>> import registration
running register(<function f1 at 0x10063b1e0>)
running register(<function f2 at 0x10063b268>)
>>> registration.registry
[<function f1 at 0x10063b1e0>, <function f2 at 0x10063b268>]通过上述测试,可以看到函数装饰器在导入模块后就立即执行,而被装饰的函数仅在明确调用的时候才运行。