问题: 带参数的装饰器,基础的实现思路是什么?
👇 点击展开/折叠 面试背诵模板(建议先背这个)
带参数的装饰器需采用三层嵌套结构:外层函数接收装饰器参数,中间层函数接收被装饰函数,内层函数接收原函数的调用参数。执行时先调用外层函数传入参数,返回中间层函数(即实际装饰器),再用该装饰器去装饰目标函数。本质是在标准闭包(两层)外加一层参数接收层,将配置参数绑定到闭包环境中供内层使用。
---
三层结构的职责划分
| 层级 |
函数 |
参数 |
职责 |
| 第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): def decorator(func): def wrapper(*args, **kwargs): 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) say_hello = decorator(say_hello)
|
保留元数据的标准写法
带参数装饰器同样需要 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'))