装饰器本质是函数对象的重新赋值,即执行@decorator等价于func = decorator(func);必须返回可调用对象,否则func变为None;依赖闭包保存原函数和参数;带参装饰器需三层嵌套;functools.wraps必用于修复元信息。

func 的名字绑定。执行 @decorator 等价于 func = decorator(func),这是最核心的事实。如果你在装饰器内部没返回一个可调用对象,被装饰函数就会变成 None,后续调用直接报 TypeError: 'NoneType' object is not callable。
常见错误包括:
return wrapper(只写了 def wrapper 但没返回)print 或日志记录就等于“完成装饰”,实际必须显式返回替换函数self 参数导致 missing 1 required positional argument
装饰器能记住 func 和传入的配置(比如 @retry(times=3) 中的 times),靠的是 Python 闭包:内部函数 wrapper 引用了外层作用域的变量,这些变量随 wrapper 一起被保留。
这意味着:
func 不会被立即执行,只是被存进闭包环境,等 wrapper 被调用时才真正运行@cache 函数各自维护自己的字典)wrapper 实例会看到相同状态——这常被用于计数器或缓存共享,但也容易引发意外副作用像 @log(level='DEBUG') 这种带参数的写法,不是语法糖,而是强制要求三阶嵌套:外层接收装饰参数,中间接收被装饰函数,内层才是实际执行逻辑的 wrapper。
结构固定为:
def log(level='INFO'):
def decorator(func):
def wrapper(*args, **kwargs):
print(f'[{level}] {func.__name__}')
return func(*args, **kwargs)
return wrapper
return decorator
漏掉任意一层都会出错:
TypeError: log() missing 1 required positional argument: 'func'
TypeError: 'function' object is not callable(因为 @log('DEBUG') 返回的是 wrapper,而非可接受 func 的装饰器)level 在 @log('DEBUG') 时求值,不是在 wrapper() 时functools.wraps 是必选项未经处理的装饰器会让 func.__name__ 变成 'wrapper',func.__doc__ 丢失,help(func) 显示错误签名。这不是 bug,是闭包函数自然行为。
修复方式唯一且标准:
wrapper 定义后立即加 @functools.wraps(func)
from functools import wraps
__name__ 或 __doc__ 单个属性——wraps 同时同步 __module__、__annotations__、__qualname__ 等共 7 项关键属性dataclasses、pydantic)依赖这些元信息做反射,不加 wraps 可能导致校验失败或 IDE 提示异常闭包和函数替换共同构成装饰器的底层骨架,但真正让装饰器“可用”的,往往是那些不起眼的元信息修复和嵌套层级控制——它们不出现在教程图解里,却决定代码能否在真实项目中长期存活。