核心要点

  • 理解装饰器本质是「接受函数返回函数」的高阶函数

  • 会用 @syntax 和 functools.wraps 保留元信息

  • 能举例带参数装饰器、类装饰器

简要回答

原理:装饰器是高阶函数——接收可调用对象,返回新的可调用对象

标准回答

原理:装饰器是高阶函数——接收可调用对象,返回新的可调用对象。

python
from functools import wraps

def log_calls(fn):
    @wraps(fn)  # 保留 __name__, __doc__
    def wrapper(*args, **kwargs):
        print(f"calling {fn.__name__}")
        return fn(*args, **kwargs)
    return wrapper

@log_calls
def train(epochs): ...
# 等价于 train = log_calls(train)

常见场景:Flask/FastAPI 路由 @app.get、pytest @fixture@lru_cache@torch.no_grad()

带参装饰器:三层嵌套 def deco(arg): def inner(fn): ... return wrapper; return inner

类装饰器:实现 __call__ 的类也可作装饰器。

常见误区

⚠️ 常见踩坑

忘记 @functools.wraps,wrapper 会覆盖原函数的 namedoc,破坏内省与文档。还有人混淆「装饰器」与「带参数装饰器」:@deco 直接装饰,@deco(arg) 是先调用 deco(arg) 拿到真正的装饰器再装饰,少写一层嵌套就会报 TypeError。多个装饰器的应用顺序也常被记反——自下而上应用。

追问

追问 1为什么需要 functools.wraps?

装饰后返回的是 wrapper,若不处理,fn.__name__ 会变成 'wrapper'、doc 丢失,影响日志、调试和文档工具(如 Sphinx、pytest)。@wraps(fn) 把原函数的 namedocwrapped 等元信息复制到 wrapper 上,保持透明。

追问 2装饰器执行顺序是什么?

多个装饰器从下往上应用:@a @b def f 等价于 f = a(b(f)),先执行 b(f)a(...)。调用时从最外层 wrapper 进入,像洋葱剥开一样逐层进入原函数。

追问 3property 和装饰器有什么关系?

@property 把方法变成描述符,访问 obj.x 时调用 getter;@x.setter 定义赋值逻辑。是装饰器在描述符协议上的应用,用于封装属性访问。

延伸学习

与本题相关的知识库文章、术语、工具与行业资讯。