如何定义带参数的装饰器
场景:实现一个装饰器,用来检查被装饰函数的参数类型,装饰器可以通过参数指明函数参数的类型,调用时如果检测出类型不匹配则抛出异常
解决方法:
提取函数签名imspect.signature() 带参数的装饰器,也就是根据参数定制化一个装饰器,可以看成生产装饰器的工厂。每次调用typeassert,返回一个特定的装饰器,然后用它去装饰其他函数
from inspect import signature
def typeassert(*ty_args, **ty_kargs): ##装饰器工厂
def decorator(func): ##装饰器
# func -> a,b
# d = {'a': int, 'b': str}
sig = signature(func) ##可以获取函数的签名,sig.parameters是参数的字典
btypes = sig.bind_partial(*ty_args, **ty_kargs).arguments ##得到一个类型绑定的映射
def wrapper(*args, **kargs):
# arg in d, instance(arg, d[arg])
for name, obj in sig.bind(*args, **kargs).arguments.items():
if name in btypes:
if not isinstance(obj, btypes[name]):
raise TypeError('"%s" must be "%s"'%(name, btypes[name]))
return func(*args, **kargs)
return wrapper
return decorator
@typeassert(int, str, list)
def f(a, b, c):
print(a, b, c)
如何实现属性可修改的函数装饰器
场景:为分析程序内哪些函数执行时间开销较大,我们定义一个带timeout参数的函数装饰器,装饰功能如下:
1. 统计被装饰函数单次调用运行时间
2. 时间大于timeout的,将此次函数调用记录到log日记中
3. 运行时可修改timeout的值
from functools import wraps
import time
import logging
def warn(timeout):
timeout = [timeout] ##python2解决timeout声明的方法
def decorator(func):
def wrapper(*args, **kargs):
start = time.time()
res = func(*args, **kargs)
used = time.time() - start
if used > timeout[0]:
msg = '"%s": %s > %s' % (func.__name__, used, timeout[0])
logging.warn(msg)
return res
def setTimeout(k):
#nonlocal timeout ##关键字声明,只有Python3支持
timeout[0] = k
wrapper.setTimeout = setTimeout
return wrapper
return decorator
from random import randint
@warn(1.5)
def test():
print('In test')
while randint(0, 1): ##随机进入睡眠
time.sleep(0.5)
for _ in range(30):
test()
test.setTimeout(1)
for _ in range(30):
test()