装饰器本质是函数套函数,利用函数为一等对象特性,通过@语法糖将被装饰函数传入装饰器并替换原函数名;带参装饰器需三层嵌套;functools.wraps必须使用以保留原函数元信息;类装饰器需实现__call__并手动同步属性。

装饰器本质是函数套函数
Python 装饰器没有魔法,它只是利用了函数是一等对象(first-class object)这一特性:函数可以被赋值给变量、作为参数传入其他函数、在函数里定义并返回。所谓 @decorator 语法糖,底层就是把被装饰函数当作参数传给 decorator,再用返回值替换原函数名。
比如:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("before")
result = func(*args, **kwargs)
print("after")
return result
return wrapper
@my_decorator
def say_hello():
print("hello")
等价于:
def say_hello():
print("hello")
say_hello = my_decorator(say_hello)关键点在于:my_decorator 必须返回一个可调用对象(通常是函数),且该对象要能接收和转发 say_hello 原本的参数 —— 否则调用会出错。
立即学习“Python免费学习笔记(深入)”;
带参数的装饰器需要三层嵌套
当你写 @decorator(arg=1) 时,Python 要求 decorator(arg=1) 的结果本身是一个装饰器(即接收函数并返回新函数)。所以必须多包一层:
- 最外层函数接收装饰器参数(如
arg),返回真正的装饰器 - 中间层是标准装饰器,接收被装饰函数,返回包装函数
- 最内层是实际执行逻辑的
wrapper
常见错误是漏掉某一层,导致 TypeError: 'function' object is not callable 或 missing 1 required positional argument: 'func'。
示例:
def repeat(times):
def decorator(func): # ← 这才是真正的装饰器
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator # ← 返回装饰器,不是 wrapper
functools.wraps 不是可选的,是必须的
如果不加 @functools.wraps(func),被装饰后的函数会丢失原始函数的元信息:__name__ 变成 'wrapper',__doc__ 为空,help() 查不到原说明,调试和反射类工具(如 Flask 路由注册、pytest 参数提取)会出问题。
正确写法:
from functools import wrapsdef my_decorator(func): @wraps(func) # ← 必须加这一行 def wrapper(*args, *kwargs): return func(args, **kwargs) return wrapper
它本质上是把 func 的 __name__、__doc__、__module__ 等属性复制到 wrapper 上。不加的话,所有基于函数签名的自动化流程都可能静默失败。
类装饰器依赖 __call__ 方法
类也可以当装饰器用,前提是实现了 __call__。实例化时传入被装饰函数,调用时行为像函数:
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
# 注意:这里仍需 wraps,否则 __name__ 丢失
from functools import wraps
self.__wrapped__ = func
self.__name__ = func.__name__
self.__doc__ = func.__doc__
def __call__(self, *args, **kwargs):
self.count += 1
return self.func(*args, **kwargs)但要注意:类装饰器无法直接用 @functools.wraps(它是为函数设计的),得手动同步属性,或继承 functools.update_wrapper 的逻辑。否则 IDE 提示、文档生成工具会识别不到原始函数信息。
装饰器的底层其实很直白,难的是在保持语义清晰的同时,不破坏函数的身份特征 —— 尤其是在组合多个装饰器、配合类型检查(mypy)或异步上下文时,元信息丢失和参数透传问题会立刻暴露。










