在掌握了基础装饰器(不带参数的函数装饰器)之后,实际工作中经常会遇到更灵活的装饰需求。下面三种进阶用法,能让你更精准地控制装饰行为。
带参数装饰器
普通装饰器直接接收被装饰函数作为参数,而带参数装饰器则允许在装饰时传入额外的配置参数,用来调整装饰器的行为(比如控制重试次数、设置日志级别等)。
实现原理:本质上是“再加一层函数包装”。外层函数接收装饰参数,返回一个真正的装饰器(内层函数),该装饰器再接收被装饰函数并对其加工。
import functools
import time
def retry(max_tries=3, delay=1):
"""带参数的装饰器:函数失败时自动重试"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_tries):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"第 {attempt+1} 次尝试失败: {e}")
if attempt == max_tries - 1:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(max_tries=4, delay=2) # 可以自定义重试次数和间隔
def fetch_data(url):
# 模拟可能失败的请求
...
@retry(max_tries=4) 实际上执行的是 retry(max_tries=4),它返回 decorator,然后 decorator 再装饰 fetch_data。这就是三层函数的妙用。
实用场景:API 调用重试、权限校验中需要传入不同角色、为函数注册时打标签等。
类装饰器
除了用函数实现装饰器,也可以用类来实现。类装饰器利用类的 call 方法,让实例可调用,完成对函数的包装。它的优势在于可以将状态保存在实例属性中,更清晰地组织复杂逻辑。
import functools
class CountCalls:
"""统计函数被调用次数的装饰器"""
def __init__(self, func):
functools.update_wrapper(self, func) # 保留原函数信息
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} 已被调用 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def greet(name):
print(f"Hello, {name}!")
greet("Alice") # 输出调用次数
greet("Bob")
执行过程:@CountCalls 相当于 greet = CountCalls(greet),此时 greet 变成了 CountCalls 的实例,但调用 greet("Alice") 会触发实例的 call 方法,从而在原来函数执行前后加上统计逻辑。
如果类装饰器也需要带参数,可以在 init 中接收配置参数,并在 call 中返回包装后的函数(此时类装饰器本身更像一个工厂)。
多层装饰器
一个函数可以被多个装饰器同时修饰。多层装饰器的执行顺序遵循“洋葱模型”:最靠近函数定义的装饰器最先应用,执行时则从外到内依次进入,再从内到外依次返回。
@decorator1
@decorator2
def func():
pass
# 等价于 func = decorator1(decorator2(func))
执行流程示例:
def bold(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return "<b>" + func(*args, **kwargs) + "</b>"
return wrapper
def italic(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return "<i>" + func(*args, **kwargs) + "</i>"
return wrapper
@bold
@italic
def hello():
return "Hello"
print(hello()) # <b><i>Hello</i></b>
- 装饰时:先
italic装饰hello,返回一个wrapper;然后bold再装饰这个wrapper。 - 调用时:先执行
bold的wrapper(添加<b>),其中调用func实际是italic的wrapper(添加<i>),最终返回的顺序是从外到内包起来。
实际应用:权限校验 + 日志记录、缓存 + 异常重试、多种格式化处理等。注意保留好 @functools.wraps,否则多层装饰下原函数信息会丢失得更严重。
掌握这三种进阶装饰器,你可以将横切关注点(如日志、认证、重试、计时)灵活地从业务代码中剥离出来,让代码复用性和可维护性再上一个台阶。