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

装饰器本质是函数对象的重新赋值
Python 装饰器运行时并不“增强”原函数,而是用新函数完全替换 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 一起被保留。
这意味着:
立即学习“Python免费学习笔记(深入)”;
-
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 提示异常
闭包和函数替换共同构成装饰器的底层骨架,但真正让装饰器“可用”的,往往是那些不起眼的元信息修复和嵌套层级控制——它们不出现在教程图解里,却决定代码能否在真实项目中长期存活。










