Python中的装饰器

前言:

装饰器比较复杂,笔者看完还是有点懵懵懂懂,这边简单记录一些装饰器实例,用以参考:
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、混合使用类和函数来实现装饰器,可以灵活满足各种场景。

三、使用wrapt模块

1、使用wrapt模块可以方便地让装饰器同时兼容函数和类方法
2、使用wrapt模块可以帮你写出结构更扁平的装饰器代码。

四、装饰器设计技巧

1、装饰器将包装调用提前到了函数被定义的位置,它的大部分优点也源于此。
2、在编写装饰器时,请考虑当前的设计是否能够很好的发挥装饰器的优势。

装饰器一般适合实现以下功能:

  1. 运行时校验:在执行阶段进行特定的校验,当校验不通过时终止执行。
    装饰器可以方便地在函数执行前介入,并可以读取所有参数辅助以校验。
    例如Django框架中的用户登录状态校验装饰器@login_required
  2. 注入额外参数:在函数被调用时自动注入额外的调用参数。
    装饰器的位置在函数头部,非常靠近参数被定义的位置,关联性强。
    例如unittest.mock模块的装饰器@patch
  3. 缓存执行结果:通过调用参数等输入信息,直接缓存函数执行结果。
    添加缓存不需要侵入函数内部逻辑,并且功能非常独立和通用。
    例如functools模块的缓存装饰器@lru_cache
  4. 注册函数:将被装饰函数注册为某个外部流程的一部分。
    在定义函数时就可以直接完成注册,关联性强。
    例如Flask框架路由注册装饰器@app.route
  5. 替换为复杂对象:将原函数(方法)替换为更复杂的对象,例如静态方法装饰器@staticmethod

3、在某些场景中,类装饰器可以替代元类,并且代码更简单。
4、装饰器装饰器模式截然不同,不要搞混。
5、装饰器里应该只有一层浅浅的包装代码,要把核心逻辑放在其他函数与类中,即浅装饰器,深实现

参考内容:《Python工匠——案例、技巧与工程实践》

你可能感兴趣的:(python,开发语言)