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工匠——案例、技巧与工程实践》

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
装饰器Python一种用于修改函数或类的行为的语法结构。它们允许在不修改原始代码的情况下,通过添加额外的功能来装饰函数或类。 装饰器实际上是一个函数,它接受一个函数作为输入,并返回一个新的函数作为输出。这个新的函数通常会在调用原始函数之前或之后执行一些额外的代码。 下面是一个简单的装饰器示例: ```python def decorator_function(original_function): def wrapper_function(): # 在调用原始函数之前执行额外的操作 print("Before the original function is called") # 调用原始函数 original_function() # 在调用原始函数之后执行额外的操作 print("After the original function is called") return wrapper_function @decorator_function def say_hello(): print("Hello!") # 调用经过装饰器修饰过的函数 say_hello() ``` 在上述示例,我们定义了一个名为`decorator_function`的装饰器函数。该装饰器接受一个名为`original_function`的函数作为参数,并返回一个新的函数`wrapper_function`。`wrapper_function`在调用原始函数之前和之后,分别打印了一些额外的信息。 通过在`say_hello`函数定义之前加上`@decorator_function`,我们将`say_hello`函数传递给了装饰器,并将装饰器返回的函数赋值给了`say_hello`。这样,当我们调用`say_hello`函数时,实际上是在调用经过装饰器修饰过的函数`wrapper_function`。 装饰器提供了一种灵活且可重复使用的方式来扩展函数的功能,比如添加日志记录、性能计时、输入验证等。在Python,还有一种更简洁的语法糖形式来使用装饰器,即使用`@`符号将装饰器应用到函数上,如上述示例的`@decorator_function`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值