当前位置: 首页 > news >正文

07-Python装饰器从入门到源码(下)-带参数装饰器与wraps

文章目录

  • Python 装饰器从入门到源码(下)——@wraps、带参数的装饰器
    • 导入语
    • 1 ~> `functools.wraps`——别让你的函数丢了身份
      • 1.1 问题根源
      • 1.2 解决方案:`@wraps`
      • 1.3 `@wraps` 做了什么
    • 2 ~> 带参数的装饰器——为什么你需要三层嵌套
      • 2.1 场景:可配置的重试装饰器
      • 2.2 逐步推导
    • 3 ~> 实战装饰器一:权限校验
    • 4 ~> 实战装饰器二:Django `@login_required` 的简化版
    • 5 ~> 装饰器为什么优先选装饰器而不是直接改函数原因总结
    • 思考 && 总结
    • 结尾

Python 装饰器从入门到源码(下)——@wraps、带参数的装饰器

📖文章简介:上篇讲完了闭包和第一个装饰器,下篇解决两个高频问题:(1) 用了装饰器后函数的__name____doc__为什么会丢,以及functools.wraps怎么修;(2) 带参数的装饰器到底是怎么工作的——为什么需要三层嵌套函数。附带三个实战装饰器:权限校验装饰器、带重试次数的网络请求装饰器、Django 的@login_required简化实现。每个装饰器都有完整的逐步拆解和可执行代码。


🎬 个人主页:源码骑士

专栏传送门:《Android开发基础》《python基础课程》

⭐️热衷从源码视角拆解技术底层原理,将复杂架构讲得通俗易懂


🎬 源码骑士的简介:
5年Android Framework系统开发经验,曾主导多项系统级性能优化专项
技术栈覆盖Android系统全链路(Binder/Handler/AMS/WMS/启动流程)及Java后端全家桶(Spring + MyBatis + Redis + Oracle)
累计产出原创技术文章100+篇,文章以源码拆解为特色,被读者评价为"看一篇胜过啃一周文档"


导入语

上篇我们写了第一个装饰器——计时器。看着很完美。但有个问题:

@timerdefadd(a,b):"""计算两个数的和"""returna+bprint(add.__name__)# 输出:wrapper ← ???不是 addprint(add.__doc__)# 输出:None ← 文档没了

用了装饰器之后,函数的元信息全丢了。如果你在用 Flask 写路由@app.route('/')__name__丢了影响还不大;但如果你的日志系统靠__name__区分函数来源、或者你写的装饰器被第三方工具用__doc__生成 API 文档时——这种信息丢失就是隐藏的生产事故。这就是functools.wraps出场的原因。下篇我们解决这个问题,顺便把带参数的装饰器也彻底搞明白。


1 ~>functools.wraps——别让你的函数丢了身份

1.1 问题根源

deftimer(func):defwrapper(*args,**kwargs):result=func(*args,**kwargs)returnresultreturnwrapper

timer(add)返回的是wrapper函数。从此add变量指向的是wrapper。所以add.__name__自然是'wrapper'

1.2 解决方案:@wraps

fromfunctoolsimportwrapsdeftimer(func):@wraps(func)# ← 关键!把 func 的元信息拷贝给 wrapperdefwrapper(*args,**kwargs):result=func(*args,**kwargs)returnresultreturnwrapper@timerdefadd(a,b):"""计算两个数的和"""returna+bprint(add.__name__)# 输出:add ✓print(add.__doc__)# 输出:计算两个数的和 ✓

1.3@wraps做了什么

本质上,@wraps(func)等价于:

wrapper.__name__=func.__name__ wrapper.__doc__=func.__doc__ wrapper.__module__=func.__module__ wrapper.__dict__.update(func.__dict__)wrapper.__wrapped__=func# 记录原始函数引用

文档、函数名、模块名、字典属性……全都从原函数复制过来。__wrapped__属性还保留了对原始函数的引用——某些调试工具靠它找回被装饰前的函数。


2 ~> 带参数的装饰器——为什么你需要三层嵌套

2.1 场景:可配置的重试装饰器

@retry(times=3,delay=1)# 你想传参数:重试3次,每次间隔1秒defcall_api():pass

普通装饰器是timer(func)。带参数的装饰器是retry(times=3)(func)——先调用retry(times=3)拿到一个装饰器,再把func传进去。

2.2 逐步推导

importtimedefretry(times,delay):defdecorator(func):# 最外层返回真正的装饰器@wraps(func)defwrapper(*args,**kwargs):forattemptinrange(times):try:returnfunc(*args,**kwargs)exceptExceptionase:ifattempt==times-1:raise# 最后一次也失败了,抛异常time.sleep(delay)returnwrapperreturndecorator@retry(times=3,delay=0.5)defunstable_network_call():importrandomifrandom.random()<0.7:raiseConnectionError("网络超时")return"成功"

三层嵌套的含义:

第1层 retry(times, delay)→ 接收装饰器参数(配置) 第2层 decorator(func)→ 接收被装饰的函数 第3层 wrapper(*args, **kwargs)→ 接收函数的调用参数

带参数的装饰器本质上是一个"返回装饰器的函数"。@retry(times=3, delay=0.5)等价于:

unstable_network_call=retry(times=3,delay=0.5)(unstable_network_call)

3 ~> 实战装饰器一:权限校验

fromfunctoolsimportwrapsdefrequire_role(role):defdecorator(func):@wraps(func)defwrapper(user,*args,**kwargs):ifuser.get("role")!=role:raisePermissionError(f"需要{role}权限,当前为{user.get('role')}")returnfunc(user,*args,**kwargs)returnwrapperreturndecorator@require_role("admin")defdelete_user(user,user_id):returnf"删除用户{user_id}"admin={"name":"张三","role":"admin"}normal={"name":"李四","role":"user"}print(delete_user(admin,42))# ✓ 删除用户 42# delete_user(normal, 42) # ❌ PermissionError: 需要 admin 权限,当前为 user

你不需要在每个敏感接口里写if user.role != "admin",一行@require_role("admin")搞定。


4 ~> 实战装饰器二:Django@login_required的简化版

Django 的@login_required装饰器就是"带条件判断的闭包",核心逻辑拆出来很简单:

fromfunctoolsimportwrapsdeflogin_required(func):@wraps(func)defwrapper(request,*args,**kwargs):ifnotrequest.get("is_authenticated"):return{"error":"未登录","redirect":"/login/"}returnfunc(request,*args,**kwargs)returnwrapper@login_requireddefprofile(request):return{"name":"张三","age":28}print(profile({"is_authenticated":True}))# {'name': '张三', 'age': 28}print(profile({"is_authenticated":False}))# {'error': '未登录', 'redirect': '/login/'}

5 ~> 装饰器为什么优先选装饰器而不是直接改函数原因总结

  • 复用:一个装饰器可以适用于多个函数
  • 解耦:权限逻辑和业务逻辑各自独立管理
  • 可读:@require_role("admin")比函数内部一堆 if 清晰得多
  • 可组合:可以叠多个装饰器:@login_required @require_role("admin") @log_api_call

思考 && 总结

下篇两个核心知识点:

  1. @wraps(func)不能省。装饰器返回的是wrapper函数,如果不做信息恢复,原函数的__name____doc__全部丢失。这是生产环境日志和文档的暗坑。
  2. 带参数的装饰器 = 三层嵌套函数。最外层接收装饰器参数(配置),中间层接收函数,最内层接收调用参数。理解这个结构之后,@app.route('/')@retry(times=3)的原理就完全通了。

结尾

各位小伙伴,装饰器上下篇到此全部结束。感谢阅读!

源码骑士 — Python 全栈 & 系统架构

👀关注:跟博主一起从源码视角深耕底层原理

❤️点赞:让优质内容被更多人看见

收藏:核心知识点存好,随用随查

💬评论:分享你的经验或疑问,一起交流

🔄一键四连:不要忘记给博主"一键四连"哦!

🗡️寄语:技术之路,同行的人会让前路更有方向

结语:装饰器是 Python 的灵魂特性之一,上篇讲闭包,下篇讲应用——两篇读完,面试不怕。一键四连别忘了!

http://www.gsyq.cn/news/1520524.html

相关文章:

  • 2026年成都婚礼筹备全攻略:信誉与实力兼备的婚庆公司深度解析 - 品牌鉴赏官2026
  • 2026年新发布:湖北市场专业的折叠标签品牌综合解析与推荐 - 品牌鉴赏官2026
  • Flink窗口实战:用Java和Lambda表达式搞定地铁客流实时统计(附完整代码)
  • 刚性结理论:从拓扑性质到多项式不变量
  • 2026年风管PVC膜市场格局观察:从材料选型看供应商综合实力 - 优质品牌商家
  • 处理AI模型输出文件?手把手教你用Python把JSONL转成标准JSON(避坑字符编码问题)
  • 用FreeGLUT和OpenGL画个彩色立方体:从glOrtho投影到矩阵变换的完整流程
  • 终极指南:Windows平台最佳漫画阅读器E-Viewer完全体验
  • 09-Python模块导入机制-sys.path与循环导入的死锁式排查
  • 2026达州旧房换窗厂家评测:适配性与服务实力对比 - 优质品牌商家
  • 2026年四川圆柱钢模板厂家实力解析:产能、交付与工程案例综合观察 - 优质品牌商家
  • 终极Windows热键侦探指南:3步定位被占用的快捷键
  • Codex使用多模型,进行项目分割.让你的用量更清晰
  • SAS与Python交互实战:复用SAS宏资产的工业级方案
  • Go爬虫实战:用Chromedp绕过网站自动化检测的3个关键Flag设置
  • HarmonyOS 6.1 沉浸式光感效果-黑色光感实现效果与过程问题解决(二)
  • 别再只盯着h=1了!Matlab adftest函数实战:用GDP数据手把手教你三种平稳性判断方法
  • 美国签证预约自动化终极指南:告别熬夜抢号的完整解决方案
  • 2026中老年旅游专列服务商评测:旅游专列咨询电话/旅游专列报名处/熊猫专列成都号/空调专列卧铺/退休专列游/退休旅游专列/选择指南 - 优质品牌商家
  • M68000指令集深度解析:位域操作与IEEE 754浮点运算实战
  • AI Native 鸿蒙 App:从页面驱动到智能驱动的架构革命
  • 2026江浙沪员工团建服务商排行:中南百草园游玩/中国龙鼓主题团建/云上草原游玩/企业团建/专业维度实测对比 - 优质品牌商家
  • 2026年哪家做动物实验比较靠谱 - 品牌排行榜
  • 从杂乱到优雅:用markdownReader在Chrome中重新定义Markdown阅读体验
  • Prompt Engineering:重构人机协作的工程化方法论
  • MC68000处理器架构深度解析:寻址模式、异常处理与协处理器指令
  • 终极指南:3步将小爱音箱改造为智能AI语音助手
  • 2026年合肥律师事务所服务能力观察:多元发展格局下的专业选择指南 - 优质品牌商家
  • 2026年更新深度解析:河北大面积银烧结实力公司全景观察 - 品牌鉴赏官2026
  • 2026年更新光彩知名的救援轮胎店:专业汽车救援服务全面解析 - 品牌鉴赏官2026