问题: 什么是装饰器,它的主要作用是什么?

👇 点击展开/折叠 面试背诵模板(建议先背这个) 装饰器本质是一个可调用对象(如函数或类),它接收一个函数作为参数,利用闭包返回一个增强后的新函数,在不修改原函数源码和调用方式的前提下为其扩展功能。主要作用是将日志记录、性能计时、权限校验、缓存等横切关注点(AOP)与核心业务逻辑解耦,提高代码复用性,符合开闭原则。Python 使用 @decorator_name 语法糖进行应用。
---

装饰器的本质与执行流程

装饰器的核心是闭包,@wrapper 语法糖在函数定义阶段即执行,等价于 func = wrapper(func)。这意味着装饰器在模块导入时就会运行,而不仅仅是在调用时。

1
2
3
4
5
6
7
8
9
10
11
def timer(func):                # 外层:接收被装饰函数
def wrapper(*args, **kwargs): # 内层:包装函数
start = time.time()
result = func(*args, **kwargs)
print(f"耗时: {time.time() - start}")
return result
return wrapper

@timer # 等价于 fast = timer(fast)
def fast():
time.sleep(0.1)

带参数的装饰器(三层结构)

当装饰器本身需要接收参数时,需要再嵌套一层函数来接收配置。

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) # 先执行 repeat(3),再应用返回的 decorator
def say():
print("Hello")

保留元数据:functools.wraps 的必要性

装饰器返回的 wrapper 会覆盖原函数的 __name__、__doc__ 等属性,导致调试困难。使用 @wraps(func) 可将这些属性从原函数复制到包装函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from functools import wraps

def logger(func):
@wraps(func) # 保留 func 的元数据
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper

@logger
def add(a, b):
"""求和函数"""
return a + b

print(add.__name__) # 输出 "add"(没有 wraps 会输出 "wrapper")
print(add.__doc__) # 输出 "求和函数"

类实现装饰器 vs 函数实现装饰器

实现方式 实现原理 适用场景
函数装饰器 外层返回内层闭包 简单装饰,无状态保持
类装饰器 __init__ 接收函数,__call__ 实现包装 需保持复杂状态或配置(如计数、统计)
1
2
3
4
5
6
7
8
9
10
11
12
13
# 类装饰器:带状态计数
class Counter:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"调用次数: {self.count}")
return self.func(*args, **kwargs)

@Counter
def process():
pass

多个装饰器的执行顺序

多个装饰器叠加时,从下往上包装,从上往下执行(调用顺序:先执行靠近函数的装饰器逻辑,再执行外层的)。