Python-装饰器的灵活实现:带参数与不带参数
作者:数据人阿多 日期:2026年2月4日
背景
装饰器是Python中一种强大的语法糖,它允许我们在不修改原函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个可调用对象,它接受一个函数作为输入,并返回一个新的函数。装饰器的使用方式非常灵活,既可以不带参数使用,也可以带参数使用
小编环境
import sys
print('python 版本:',sys.version.split('|')[0])
#python 版本: 3.11.11
函数装饰器实现
我们先来看一个简单的函数装饰器实现:
import time
def delayed_start(func=None, *, duration=1):
def decorator(_func):
def wrapper(*args, **kwargs):
print(f"Wait for {duration} seconds before starting...")
time.sleep(duration)
return _func(*args, **kwargs)
return wrapper
if func is None:
return decorator
else:
return decorator(func)
这个装饰器可以以两种方式使用:
- 不带参数使用:当装饰器不带参数使用时,Python会直接将装饰的函数作为参数传递给
delayed_start
@delayed_start
def hello_no_arg(name="datashare"):
print("from hello_no_arg, param name =", name)
等价于:
hello_no_arg = delayed_start(hello_no_arg)
- 带参数使用:当装饰器带参数使用时,Python会先调用装饰器函数,返回一个真正的装饰器,然后再用这个装饰器装饰函数
@delayed_start(duration=2)
def hello_with_arg(name="datashare"):
print("from hello_with_arg, param name =", name)
等价于:
hello_with_arg = delayed_start(duration=2)(hello_with_arg)
实现原理
delayed_start函数的巧妙之处在于它通过检查 func参数是否为 None来判断装饰器的使用方式:
- 如果
func不是None,说明是不带参数使用,直接返回decorator(func) - 如果
func是None,说明是带参数使用,返回decorator函数等待接收真正的函数参数
类装饰器实现
类装饰器通过实现 __call__方法来实现装饰器的功能。下面是带参数和不带参数的类装饰器实现:
from functools import wraps
import time
class Timer:
def __init__(self, func=None, *, print_args=False):
self.func = func
self.print_args = print_args
def __call__(self, *args, **kwargs):
# 情况 1:@Timer(print_args=True)
# 第一次 __call__,args[0] 是函数
if self.func is None:
func = args[0]
return self._decorate(func)
# 情况 2:@Timer
# 或者已经绑定好函数,真正执行
return self._decorate(self.func)(*args, **kwargs)
def _decorate(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
st = time.perf_counter()
ret = func(*args, **kwargs)
if self.print_args:
print(f'"{func.__name__}", args: {args}, kwargs: {kwargs}')
print(f"time cost: {time.perf_counter() - st:.4f} seconds")
return ret
return wrapper
- 不带参数使用:
@Timer
def compute(x):
time.sleep(1)
return x * 2
当类装饰器不带参数使用时,Python会创建 Timer类的实例,并将 compute函数传递给 __init__方法。此时 self.func就是 compute函数。当我们调用 compute(10)时,实际上是调用 Timer实例的 __call__方法
- 带参数使用:
@Timer(print_args=True)
def compute2(x):
time.sleep(1)
return x * 3
当类装饰器带参数使用时,Python会先调用 Timer(print_args=True)创建实例,此时 self.func为 None。然后用这个实例去装饰 compute2函数,这相当于调用 Timer实例的 __call__方法,并将 compute2作为参数传入
两种实现方式的比较
| 特性 | 函数装饰器 | 类装饰器 |
|---|---|---|
| 代码简洁性 | 更简洁,适合简单的装饰逻辑 | 更复杂,但结构更清晰 |
| 状态管理 | 需要使用闭包或nonlocal变量 | 可以使用实例属性,更直观 |
| 可扩展性 | 适合简单的装饰功能 | 适合需要维护状态的复杂装饰器 |
| 可读性 | 对于简单场景更易读 | 对于复杂场景更易维护 |
实际应用场景
- 性能监控:如
Timer装饰器,用于测量函数执行时间 - 权限验证:检查用户是否有权限执行某个函数
- 日志记录:自动记录函数的调用和参数
- 缓存:实现函数结果的缓存,提高性能
- 重试机制:当函数执行失败时自动重试
最佳实践
- 使用
functools.wraps装饰器来保留原函数的元数据(如函数名、文档字符串等) - 对于简单的装饰器,优先使用函数装饰器
- 对于需要维护状态的装饰器,考虑使用类装饰器
- 在设计带参数的装饰器时,确保同时支持带参数和不带参数的使用方式
总结
Python装饰器是一个强大而灵活的特性,它允许我们以非侵入式的方式增强函数的功能。通过掌握函数装饰器和类装饰器的实现方式,以及如何实现带参数和不带参数的装饰器,我们可以编写出更加通用和可重用的代码。无论是简单的函数增强还是复杂的状态管理,装饰器都能提供优雅的解决方案
理解装饰器的工作原理不仅有助于我们编写更好的装饰器,还能加深我们对Python函数式编程和元编程的理解,是成为高级Python开发者的重要一步
历史相关文章
以上是自己实践中遇到的一些问题,分享出来供大家参考学习,欢迎关注微信公众号:DataShare ,不定期分享干货