Python装饰器从入门到生产实践:闭包、functools.wraps 与三层嵌套一次讲透
Python 的装饰器是面试高频题,也是很多工程师"会用但说不清"的知识点。今天用一篇短文把装饰器从闭包讲到生产实践,帮你把这个知识点彻底吃透。
一、装饰器的本质:闭包 + 语法糖
装饰器就是一个"接收函数、返回新函数"的调用。@decorator 只是 func = decorator(func) 的语法糖。理解它的前提是理解闭包:内层函数引用了外层作用域的变量,并且这个引用在函数返回后依然存活。
最简示例:
def log_call(func):
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_call
def query_user(uid):
...
调用 query_user(1) 时实际执行的是 wrapper(1),它先打印日志再调用原函数。
二、三个必踩的坑
1. 丢失元信息。wrapper 覆盖了原函数的 __name__、__doc__,调试和文档生成全乱。解法:给 wrapper 加上 functools.wraps(func),一行解决。
2. 装饰带返回值的函数忘记 return。wrapper 里只写 func(*args, **kwargs) 而不 return,调用方拿到的永远是 None。这类 bug 在重构时经常出现,单测能兜住。
3. 装饰器只在导入时执行一次。想用装饰器做"每次调用都生效"的逻辑,必须写在 wrapper 内部;写在装饰器函数体里的代码只在被装饰那一刻跑一遍。
三、带参数的装饰器:三层嵌套
@retry(times=3, delay=1)
def fetch(url): ...
带参数的装饰器是三层结构:最外层接收参数,中层接收函数,内层是 wrapper。写的时候记住"装饰器工厂"这个叫法就不容易乱:retry(times=3) 返回真正的装饰器,再由它包装 fetch。
四、生产中的高频应用
1. 重试与熔断:对网络请求包一层指数退避重试,注意只对幂等操作重试,POST 下单这类接口重试可能造成重复扣款。
2. 缓存:functools.lru_cache 是现成的装饰器,适合纯函数;带过期时间的缓存需要自己写,注意缓存 key 要把参数序列化进去。
3. 权限与审计:Web 框架的路由守卫本质就是装饰器,Flask 的 @app.route、鉴权切面都是同一套路。
4. 性能埋点:用 time.perf_counter 包住函数计时,比在业务代码里插 print 干净得多。
五、一条心法
装饰器适合做"与业务无关的横切逻辑":日志、重试、缓存、鉴权。一旦装饰器里开始出现业务 if 判断,就该停下来想想是不是抽象错了。保持装饰器无业务语义,它才能在整个项目里复用。
把闭包、functools.wraps、三层嵌套这三件事讲清楚,装饰器就不神秘了。下次面试被问到,不妨从"它只是函数调用的一种写法"开始讲起。
- 点赞
- 收藏
- 关注作者
评论(0)