装饰器比较复杂,笔者看完还是有点懵懵懂懂,这边简单记录一些装饰器实例,用以参考:
1、打印函数耗时的无参数装饰器timer
def timer(func):
# decorated即为包装函数
def decorated(*args, **kwargs):
st = time.perf_counter()
ret = func(*args, **kwargs)
time_cost = time.perf_counter() - st
print(f'time cost: {time_cost } seconds')
return ret
return decorated
@timer
def random_sleep():
time.sleep(random.random())
2、增加print_args的有参装饰器timer
def timer(print_args=False):
# 可以看到,带参数的装饰器有两层嵌套
@wraps(func)
def decorator(func):
def wrapper(*args, **kwargs):
st = time.perf_counter()
ret = func(*args, **kwargs)
time_cost = time.perf_counter() - st
if print_args:
print('args: {args}, kwargs: {kwargs}')
print(f'time cost: {time_cost } seconds')
return ret
return wrapper
return decorator
@timer(print_args=True)
def sleep_somme_time(sleep_time):
time.sleep(sleep_time))
# 即使参数有默认值,调用上述装饰器是也必须带括号
@timer()
def sleep_somme_time(sleep_time):
time.sleep(sleep_time))
3、定义了可选参数的装饰器delayed_start
def delayed_start(func=None, *, duration=1):
@wraps(func)
def decorator(_func):
def wrapper(*args, **kwargs):
print(f'Wait for {duration} seconds')
time.sleep(duration)
return _func(*args, **kwargs)
return wrapper
if func is None:
return decorator
else:
return decorator(func)
# 1、不提供参数
@delayed_start
def hello():
...
# 2、提供可选的关键字参数
@delayed_start(duration=2)
def hello():
...
# 3、提供括号调用,但不提供参数
@delayed_start()
def hello():
...
"""
在上述的代码中,将所有参数都转变为了关键字参数。
当func为None时,代表使用方提供了关键字参数,比如@delayed_start(duration=2),此时返回接受单个函数参数内层自装饰器decorator。
当func不为None时,代表使用方没有提供关键字参数,直接用了无括号的@delayed_start调用方式,此时返回内层包装函数wrapper。
"""
# 另一种写法
def delayed_start(func=None, *, duration=1):
if func is None:
return partial(delayed_start, duration=duration)
@wraps(func)
def wrapper(*args, **kwargs):
print(f'Wait for {duration} seconds')
time.sleep(duration)
return func(*args, **kwargs)
return wrapper
1、装饰器最常见的实现方式,是利用闭包原理通过多层嵌套函数实现。
2、在实现装饰器时,请记得使用wraps()
更新包装函数的元数据。
3、wraps()
不光可以保留元数据,还能保留包装函数的额外属性。
简而言之,实现装饰器时都用
@wraps(func)
修饰包装函数。
4、利用仅限关键字参数,可以很方便地实现可选参数的装饰器。
1、只要是可调用的对象,都可以用作装饰器。
2、实现了__call__
方法的类实例可调用。
3、基于类的装饰器分为两种:“函数替换”和“实例替换”。
4、“函数替换” 装饰器与普通装饰器没什么区别,只是嵌套层级更少。
5、通过类实现 “实例替换” 装饰器,在管理状态和追加行为上有天然的优势。
6、混合使用类和函数来实现装饰器,可以灵活满足各种场景。
1、使用wrapt
模块可以方便地让装饰器同时兼容函数和类方法。
2、使用wrapt
模块可以帮你写出结构更扁平的装饰器代码。
1、装饰器将包装调用提前到了函数被定义的位置,它的大部分优点也源于此。
2、在编写装饰器时,请考虑当前的设计是否能够很好的发挥装饰器的优势。
装饰器一般适合实现以下功能:
- 运行时校验:在执行阶段进行特定的校验,当校验不通过时终止执行。
装饰器可以方便地在函数执行前介入,并可以读取所有参数辅助以校验。
例如Django框架中的用户登录状态校验装饰器@login_required
。- 注入额外参数:在函数被调用时自动注入额外的调用参数。
装饰器的位置在函数头部,非常靠近参数被定义的位置,关联性强。
例如unittest.mock
模块的装饰器@patch
。- 缓存执行结果:通过调用参数等输入信息,直接缓存函数执行结果。
添加缓存不需要侵入函数内部逻辑,并且功能非常独立和通用。
例如functools
模块的缓存装饰器@lru_cache
。- 注册函数:将被装饰函数注册为某个外部流程的一部分。
在定义函数时就可以直接完成注册,关联性强。
例如Flask框架路由注册装饰器@app.route
。- 替换为复杂对象:将原函数(方法)替换为更复杂的对象,例如静态方法装饰器
@staticmethod
。
3、在某些场景中,类装饰器可以替代元类,并且代码更简单。
4、装饰器和装饰器模式截然不同,不要搞混。
5、装饰器里应该只有一层浅浅的包装代码,要把核心逻辑放在其他函数与类中,即浅装饰器,深实现。
参考内容:《Python工匠——案例、技巧与工程实践》