带参数装饰器和普通装饰器有什么区别?
🔹 面试官问的这个问题挺常见的,我从三个层面来拆,其实本质就一句话:带参装饰器比普通装饰器多包了一层函数。
1️⃣ 定义方式:多了一层“壳”¶
- 普通装饰器直接就是一个函数,接收
func,返回新函数(或可调用对象)。
# 普通装饰器:两层结构
def my_decorator(func):
def wrapper(*args, **kwargs):
print("before")
result = func(*args, **kwargs)
print("after")
return result
return wrapper
-
👆 结构就是
decorator → wrapper。 -
带参数装饰器需要用“函数工厂”再包一层,因为要接收配置参数,然后返回真正的装饰器。
# 带参装饰器:三层结构
def repeat(times):
def decorator(func): # 这才是真正的装饰器
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
- 👆 结构变成
参数工厂 → decorator → wrapper,多了一层。
🧠 记忆点:普通装饰器是 @dec 直接拿函数;带参装饰器是 @dec(arg) 先调用工厂拿到装饰器,再用它去装饰函数。
2️⃣ 执行时机:一个在导入时,一个在调用时再多一步¶
-
普通装饰器:
@my_decorator等价于func = my_decorator(func),在模块加载时就执行装饰器函数,得到包装后的函数。 -
带参装饰器:
@repeat(3)实际上是func = repeat(3)(func),先执行repeat(3)返回decorator,再立即把func传进去。 🕐repeat(3)这一步在定义函数时就执行了(跟普通装饰器外层一样),只不过它返回的是一个装饰器,紧接着执行内层装饰。
所以时间线上没有本质延迟,但多了一次函数调用,意味着你可以在 repeat(3) 里做参数校验、闭包捕获等额外工作。
3️⃣ 灵活性 & 应用场景¶
✅ 比如 Flask 的 @app.route('/home') 就是典型的带参装饰器,路径是参数,框架先拿到路径,返回一个注册装饰器,再装饰视图函数。
🧩 一个直觉类比¶
-
普通装饰器:像给手机贴一张固定的钢化膜,所有手机贴一样的膜。
-
带参装饰器:像贴膜机,你可以设置参数(磨砂/防蓝光),机器再生产相应的膜,再去贴。
理解了这层“工厂返回装饰器”的机制,几乎所有带参装饰器的写法都能自己推出来。
我一般面试讲到这,对方就不会再追问了。顺便补一句,如果你能再深入说一下用 functools.wraps 保留元信息和用类实现装饰器,基本上这道题就拉满了。👌