装饰器本质是接收函数并返回函数的高阶函数,非语法糖;@decorator 等价于 func = decorator(func);无参装饰器需三层结构并 return wrapper;有参装饰器需四层,先传参再返回装饰器;执行时机在 import 阶段。

装饰器本质是函数套娃,不是语法糖魔法
Python 装饰器就是个接收函数、返回函数的高阶函数。它不改原函数代码,只在调用前后加逻辑——比如打日志、计时、权限校验。别被 @ 符号骗了,@log_time 等价于 func = log_time(func),本质就是赋值。
常见错误现象:TypeError: 'NoneType' object is not callable,多半是装饰器内部忘了 return 包装后的函数;或者用了 print 代替 return。
- 无参装饰器必须返回一个可调用对象(通常是闭包函数)
- 被装饰函数的
__name__、__doc__默认会被覆盖,要用functools.wraps修复 - 装饰器本身不能带括号调用,否则 Python 会报
TypeError: 'function' object is not callable
写无参装饰器:三行结构要刻进肌肉记忆
最简无参装饰器就三部分:外层接收被装饰函数,内层接收参数,最里执行原逻辑。记不住就默写这骨架:
def simple_log(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs) # 必须 return!
return wrapper使用场景:快速加日志、调试入口、统一异常捕获。
立即学习“Python免费学习笔记(深入)”;
-
func是原始函数对象,只在装饰时运行一次 -
wrapper才是真正被调用的,每次执行都走这里 - 漏掉
return func(...)→ 原函数返回值永远是None - 没加
@functools.wraps(func)→help(wrapper)看不到原函数 docstring
写有参装饰器:多套一层函数,参数在最外层
带参数的装饰器(如 @retry(times=3))本质是「返回装饰器的工厂函数」。它比无参多一层嵌套,参数在最外层函数接收。
常见错误现象:TypeError: retry() missing 1 required positional argument: 'func',说明你少套了一层,把 retry 当成装饰器用了,其实它得先被调用生成装饰器。
from functools import wraps <p>def retry(times=2): def decorator(func): @wraps(func) def wrapper(*args, *<em>kwargs): for i in range(times): try: return func(</em>args, **kwargs) except Exception: if i == times - 1: raise return None return wrapper return decorator
- 调用顺序:先
retry(times=3)→ 返回decorator→ 再用它去装饰func - 参数类型影响行为:比如
cache(maxsize=128)中maxsize控制 LRU 缓存大小,不是装饰器本身的配置 - 如果参数是可变对象(如
list),注意别在闭包里意外共享状态
装饰器执行时机容易被忽略:装饰发生在 import 时,不是调用时
所有 @ 开头的装饰器,在模块加载(import)阶段就执行了外层函数。这意味着:初始化耗时操作、读配置、连数据库等逻辑,只要写在装饰器定义里,就会在 import 时触发——哪怕函数根本没被调用过。
性能影响明显:一个装饰器里写了 requests.get("https://api.example.com/config"),那每次导入这个模块,都会发请求。
- 想延迟到第一次调用再初始化?把逻辑挪进
wrapper里,用if not initialized:控制 - 想兼容热重载或测试 mock?避免在装饰器顶层做副作用操作(如写文件、改全局变量)
- 多个装饰器叠加(
@a @b @c)时,执行顺序是自下而上装饰、自上而下运行,这点和洋葱模型一致










