python装饰器
在上一篇博客《流畅的python》之 设计模式, 装饰器中介绍了python装饰器。
定义一个函数装饰器,它会在每次调用被装饰的函数的时候计时,然后把经过的时间,传入的参数和调用结果打印出来:
import time
def clock(func): # 定义函数装饰器
def clocked(*args): # 接受任意一个定位函数
t0 = time.perf_counter()
result = func(*args)
elapse = time.perf_counter() - t0
name = func.__name__
args_str = ",".join([repr(arg) for arg in args])
print("[%0.8fs] %s (%s) -> %r" % (elapse, name, args_str, result))
return result
return clocked
@clock
def snooze(seconds):
print("nihaode")
time.sleep(seconds)
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
if __name__ == "__main__":
snooze(2)
# 调用snooze,相当于clock(snooze(para)), clock返回clocked, 执行的是clocked(para)
factorial(5)
在上面的代码中,factorial()会作为参数传递给clock,然后clock函数会返回clocked函数,python解释器会把clocked函数赋值给factorial,即相当于factorial=clock(factorial),因此,factorial保存的是clocked函数的引用。所以每次调用factorial(n)相当于调用clocked(n)
这是装饰器的典型行为,吧被装饰的函数替换成新的函数,而这接受相同的参数,而且返回被装饰的函数本该返回的值,还会做一些额外的操作
装饰器:“动态的给一个对象添加一些额外的职责”
2. functools中的装饰器
functools.lru_cache实现了备忘功能:是一项优化功能,他把耗时的函数结果保存起来,避免传入相同的参数时重复计算。但缓存不会无限增长,一段时间不用的会被扔掉。 lru(Least Recently used)
例如定义一个计算斐波那契额数列的函数:
使用clock装饰:
使用lru_cache装饰:
import time
import functools
def clock(func): # 定义函数装饰器
@functools.wraps(func)
def clocked(*args): # 接受任意一个定位函数
t0 = time.perf_counter()
result = func(*args)
elapse = time.perf_counter() - t0
name = func.__name__
args_str = ",".join([repr(arg) for arg in args])
print("[%0.8fs] %s (%s) -> %r" % (elapse, name, args_str, result))
return result
return clocked
@clock
def factorial(n):
return n if n <2 else n*factorial(n-1)
@clock
def fib(n):
if n < 2:
return n
else:
return fib(n-2) + fib(n-1)
@functools.lru_cache() # 装饰器叠放,将@functools.lru_cache()应用到@clock返回的函数上面
@clock
def fib1(n):
if n < 2:
return n
else:
return fib1(n-1) + fib1(n-2)
if __name__ == "__main__":
# snooze(2)
# 调用snooze,相当于clock(snooze(para)), clock返回clocked, 执行的是clocked(para)
# factorial(5)
fib(20)
print('-'*30)
fib1(20)
在fib()函数中,每次进行递归,都会有大量的值需要重新计算,这是主要浪费时间的地方:
而在lru_cache()装饰过的函数,会将以前的值缓存下来:
所以消耗的时间会大大减少!
除了优化递归算法外,lru_cache在从web中获取信息的应用中也有很大的作用
lru_cache()也有参数:
@functools.lru_cache(maxsize=128, typed=False) # 装饰器叠放,将@functools.lru_cache()应用到@clock返回的函数上面
@clock
def fib1(n):
if n < 2:
return n
else:
return fib1(n-1) + fib1(n-2)
maxsize:保存调用结果的个数,设为2的次幂,缓存满了之后,将旧的结果丢掉,再保存新的
typed: 是否将不同参数类型得到的结果分开保存(传入10和10.0得到的结果)
单分派泛函数:singleispatch
假设需要定义函数process(para),需要实现功能:
1.如果para是数字:则输出<number>para+100<para>
2.如果para是str: 则输出<string>para.replace(" ", "*")<string>
3.如果para的是list, 则输出<list>average(para)<list>
实现方案:
1. 编写函数,通过if,else判断参数类型。执行相应的操作:
2.通过装饰器实现:
functools中的函数singleispatch(单分派泛函数),就能实现此功能
因为python不能像C++一样实现函数重载,所以使用装饰器将普通函数变成泛函数,使之成为根据不同参数类型执行相应操作的一组函数:
from functools import singledispatch
import numbers
@singledispatch # 标记处理不同参数的基函数
def process(para): # 如果是没有注册的类型,就按照这种方式处理
content = para
return "<prep>{}<prep>".format(content)
# 定义处理各个参数的专门函数
@process.register(numbers.Integral) # 注册参数类型为Integral的处理函数
def _(num): # 这里函数名取什么无所谓,所以直接省略为_
content = num + 100
return "<number>{}<number>".format(content)
@process.register(str) # 注册参数类型为str的处理函数
def _(string):
content = string.replace(' ', '*')
return "<string>{}<string>".format(content)
@process.register(list)
def _(list_para):
content = sum(list_para) / len(list_para)
return "<list>{}<list>".format(content)
if __name__ == "__main__":
print(process(200)) # 数字处理函数
print(process("Hello world")) # 字符串处理函数
print(process([12, 34, 10, 40])) # 列表处理函数
print(process((12, 56, 34, 56))) # 没有注册的tuple类型,按照默认方式处理
函数的输出结果:
也可以叠放多个装饰器,让同一个函数支持不同类型。
singleispatch机制能够使我们在系统的任何地方或者任何模块中定义新的专门处理函数。它支持模块化扩展,如果在模块中定义了新的类型,可以注册新的函数来处理这个类型。
装饰器的叠放特性:
如何构建接收参数的装饰器:
python将被装饰的函数作为第一个参数传递给装饰器函数,如何让装饰器接受其他的参数:
可以通过创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。
registry = set()
def register(active=True): # 这个是装饰器工厂函数,返回的是一个装饰器
def decorate(func): # 这个才是装饰器
print("Running register(active=%s)->decorate(%s)" % (active, func))
if active:
registry.add(func) # 注册被装饰的函数
else:
registry.discard(func) # 不装饰,如果有从列表中删除
return func # 由装饰器返回的函数
return decorate # 返回一个装饰器
@register() # 加括号,以函数的形式调用
def f1():
print("Running f1()")
@register(active=False) # 实际返回的是一个装饰器,再装饰f2()
def f2():
print("Running f2()")
def f3():
print("Running f3()")
if __name__ == "__main__":
f1()
f2()
f3()
print(registry)
结果:
以上面的clock装饰器为例:将他修改为具有参数的装饰器
import time
import functools
# 定义有参数的装饰器 装饰器工厂
default_fmt = "[{elapsed:0.8f}s] {name} ({args}) -> {result}"
def clock(fmt=default_fmt): # 装饰器功能工厂函数,返回的是装饰器
def decorate(func): # 装饰器
def clocked(*args):
t0 = time.time()
result = func(*args)
elapsed = time.time() - t0
name = func.__name__
args = ",".join([repr(x) for x in args])
# result = repr(result)
# locals()参数匹配
print(fmt.format(**locals())) # locals Return a dictionary containing the current scope's local variables.
return result
return clocked
return decorate
@clock()
def factorial(n):
return n if n <2 else n*factorial(n-1)
@clock()
def fib(n):
if n < 2:
return n
else:
return fib(n-2) + fib(n-1)
@functools.lru_cache(maxsize=128, typed=False) # 装饰器叠放,将@functools.lru_cache()应用到@clock返回的函数上面
@clock(fmt="{name}({args})-->{elapsed}") # 这里使用了locals,所以参数会进行自动匹配
def fib1(n):
if n < 2:
return n
else:
return fib1(n-1) + fib1(n-2)
if __name__ == "__main__":
# snooze(2)
# 调用snooze,相当于clock(snooze(para)), clock返回clocked, 执行的是clocked(para)
# factorial(5)
fib(5)
fib1(5)
# print('-'*30)
# fib1(100)
输出结果:
装饰器的参数用于控制输出格式
----------------------------------------------------------------------------------------------------------------