装饰器本质是函数调用链的语法糖,即@decorator等价于func = decorator(func),要求装饰器返回可调用对象,多层装饰器执行顺序自下而上、包装顺序自上而下。
Python 装饰器不是魔法,它只是把 @decorator 写法自动转成 func = decorator(func)。关键在于:被装饰函数会被当作参数传给装饰器,而装饰器必须返回一个可调用对象(通常是新函数)。如果返回非可调用对象,后续调用会直接报 TypeError: 'NoneType' object is not callable。
__call__)@decorator 必须出现在函数定义的正上方,且紧邻,中间不能有空行或注释当你看到 @retry(max_attempts=3) 这种写法,说明这不是装饰器本身,而是「装饰器工厂」——它返回真正的装饰器。这类结构强制要求三层嵌套:外层接收装饰器参数,中层接收被装饰函数,内层是实际执行逻辑。
def retry(max_attempts=3):
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if i == max_attempts - 1:
raise
return None
return wrapper
return decoratorTypeError: decorator() missing 1 required positional argument: 'func'
func(*args, **kwargs),这会让装饰器立即执行,而不是延迟到函数调用时functoo
ls.wraps(func) 包裹内层 wrapper,否则被装饰函数的 __name__、__doc__ 会变成 wrapper 的信息当需要在多次调用间共享状态(如计数、缓存、连接池),类装饰器比闭包更清晰。它的核心是实现 __init__(接收被装饰函数)和 __call__(替代原函数行为)。
class CountCalls:
def __init__(self, func):
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"{self.func.__name__} has been called {self.count} times")
return self.func(*args, **kwargs)@CountCalls
def say_hello():
return "hello"
@functools.wraps,需手动复制元数据(如 self.__name__ = func.__name__)或继承 functools.update_wrapper
__get__ 方法,它还能支持方法装饰(即用于类内 def),否则对实例方法会出错:缺少 self 参数__init__ 中为每个函数单独建状态容器)装饰器会改变原始函数的调用栈,导致 traceback 显示的是 wrapper 而非真实函数名,给排查问题带来干扰。尤其在日志、监控、性能分析工具中,这种「假栈帧」会让定位变慢。
import inspect; inspect.stack() 查看当前调用栈,确认是否有多余的 wrapper 层functools.wraps,不只是为了 __name__,它还会同步 __annotations__ 和 __dict__
async def)不能直接用同步装饰器包装,否则会报 RuntimeWarning: coroutine 'wrapper' was never awaited;必须写专门的 async def wrapper 并用 await func()
真正难的不是写出能跑的装饰器,而是让它的行为在元编程层面保持透明、可测、可维护。多数线上 bug 都藏在装饰器对异常传播、上下文变量、协程生命周期的隐式修改里。