问题: 带参数的装饰器,基础的实现思路是什么?

👇 点击展开/折叠 面试背诵模板(建议先背这个) 带参数的装饰器需采用三层嵌套结构:外层函数接收装饰器参数,中间层函数接收被装饰函数,内层函数接收原函数的调用参数。执行时先调用外层函数传入参数,返回中间层函数(即实际装饰器),再用该装饰器去装饰目标函数。本质是在标准闭包(两层)外加一层参数接收层,将配置参数绑定到闭包环境中供内层使用。
---

三层结构的职责划分

层级 函数 参数 职责
第1层 decorator_with_args(arg) 装饰器参数(如 times=3) 接收配置,返回第2层函数
第2层 actual_decorator(func) 被装饰函数 接收目标函数,返回第3层包装函数
第3层 wrapper(*args, **kwargs) 原函数的调用参数 执行增强逻辑,调用原函数,返回结果

代码模板(计时器 + 重复次数)

1
2
3
4
5
6
7
8
9
10
11
12
def repeat(times):                     # 第1层:接收参数
def decorator(func): # 第2层:接收函数
def wrapper(*args, **kwargs): # 第3层:接收调用参数
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator

@repeat(times=3) # 语法糖应用
def say_hello(name):
print(f"Hello, {name}")

语法糖的底层展开过程

带参数装饰器的执行分为两步:

1
2
3
4
5
6
7
8
9
10
11
# 源码:
@repeat(times=3)
def say_hello(name):
print(f"Hello, {name}")

# 等价于(先执行外层,再执行返回的装饰器):
def say_hello(name):
print(f"Hello, {name}")

decorator = repeat(times=3) # 第1步:执行外层,获取实际装饰器
say_hello = decorator(say_hello) # 第2步:执行装饰器,获取包装函数

保留元数据的标准写法

带参数装饰器同样需要 functools.wraps 保留被装饰函数的 __name__ 和 __doc__:

1
2
3
4
5
6
7
8
9
10
11
from functools import wraps

def repeat(times):
def decorator(func):
@wraps(func) # 保留原函数元数据
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator

对比:无参数装饰器 vs 带参数装饰器

对比维度 无参数装饰器 带参数装饰器
嵌套层数 2 层(外层接收函数,内层包装) 3 层(外层接收参数,中层接收函数,内层包装)
语法糖 @deco @deco(args)
底层展开 func = deco(func) func = deco(args)(func)
参数传递 仅传递被装饰函数 先传配置参数,再传被装饰函数

常见应用场景

  • 重复执行(如 @repeat(3))
  • 延时重试(如 @retry(max_attempts=5, delay=1))
  • 路由注册(如 @app.route('/home'))
  • 权限控制(如 @require_permission('admin'))